/* -LICENSE-START-
 ** Copyright (c) 2015 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-
 */

#include "DX11Composite.h"
#include <directxmath.h>
#include "cube_VS.h"
#include "cube_PS.h"
#include "frame_VS.h"
#include "frame_PS.h"

using namespace DirectX;

#define SAFE_RELEASE(x) if(x){ (x)->Release(); (x) = NULL; }


//--------------------------------------------------------------------------------------
// Structures
//--------------------------------------------------------------------------------------
struct SimpleVertex
{
	XMFLOAT3 Pos;
	XMFLOAT2 Tex;
	XMFLOAT4 Col;
};

struct CBView
{
	XMMATRIX mView;
};

struct CBProjection
{
	XMMATRIX mProjection;
};

struct CBChangesEveryFrame
{
	XMMATRIX mWorld;
};

DX11Composite::DX11Composite(HWND hWnd, HDC hDC) :
	mhWnd(hWnd), mhDC(hDC),
	mDriverType(D3D_DRIVER_TYPE_NULL),
	mFeatureLevel(D3D_FEATURE_LEVEL_11_0),
	mpD3DDevice(NULL), mpImmediateContext(NULL),
	mpSwapChain(NULL), mpRenderTargetView(NULL),
	mpBackBuffer(NULL), mpDepthStencil(NULL),
	mpDepthStencilView(NULL),
	mpCubeVertexShader(NULL), mpCubeFragmentShader(NULL),
	mpFrameVertexShader(NULL), mpFrameFragmentShader(NULL),
	mpSamplerLinear(NULL), mpVertexLayout(NULL),
	mpCubeVertexBuffer(NULL), mpCubeIndexBuffer(NULL),
	mpFrameVertexBuffer(NULL), mpFrameIndexBuffer(NULL),
	mpXVertexBuffer(NULL), mpXIndexBuffer(NULL),
	mpCBViewCube(NULL), mpCBPerspectiveProjection(NULL),
	mpCBChangesEveryFrame(NULL),
	mCaptureDelegate(NULL), mPlayoutDelegate(NULL),
	mDLInput(NULL), mDLOutput(NULL),
	mCaptureAllocator(NULL), mPlayoutAllocator(NULL),
	mFrameWidth(0), mFrameHeight(0),
	mHasNoInputSource(true),
	mFastTransferExtensionAvailable(false),
	mpCaptureTexture(NULL), mpPlayoutTexture(NULL),
	mpPlayoutTextureRTV(NULL),
	mpBlendStateAlphaBlend(NULL), mpBlendStateNoBlend(NULL),

	mRotateAngle(0.0f),
	mRotateAngleRate(0.0f)
{
	InitializeCriticalSection(&pMutex);
}

DX11Composite::~DX11Composite()
{
	CleanupD3D();

	// Cleanup for Capture
	if (mDLInput != NULL)
		mDLInput->SetCallback(NULL);

	SAFE_RELEASE(mDLInput);
	SAFE_RELEASE(mCaptureDelegate);
	SAFE_RELEASE(mCaptureAllocator);

	// Cleanup for Playout
	if (mDLOutput != NULL)
		mDLOutput->SetScheduledFrameCompletionCallback(NULL);

	SAFE_RELEASE(mDLOutput);
	SAFE_RELEASE(mPlayoutDelegate);
	SAFE_RELEASE(mPlayoutAllocator);

	DeleteCriticalSection(&pMutex);
}

bool DX11Composite::InitDeckLink()
{
	bool							bSuccess = false;
	IDeckLinkIterator*				pDLIterator = NULL;
	IDeckLink*						pDL = NULL;
	IDeckLinkAttributes*			deckLinkAttributes = NULL;
	IDeckLinkDisplayModeIterator*	pDLDisplayModeIterator = NULL;
	IDeckLinkDisplayMode*			pDLDisplayMode = NULL;
	BMDDisplayMode					displayMode = bmdModeHD1080i5994;		// mode to use for capture and playout
	float							fps;
	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;
	}

	while (pDLIterator->Next(&pDL) == S_OK)
	{
		BOOL supportsFullDuplex = FALSE;

		if (result = pDL->QueryInterface(IID_IDeckLinkAttributes, (void**)&deckLinkAttributes) != S_OK)
		{
			printf("Could not obtain the IDeckLinkAttributes interface - result %08x\n", result);
			SAFE_RELEASE(pDL);
			continue;
		}

		if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsFullDuplex, &supportsFullDuplex) != S_OK)
			supportsFullDuplex = FALSE;

		SAFE_RELEASE(deckLinkAttributes);

		// Use a full duplex device as capture and playback, or half-duplex device
		// as capture or playback.
		bool inputUsed = false;
		if (!mDLInput && pDL->QueryInterface(IID_IDeckLinkInput, (void**)&mDLInput) == S_OK)
			inputUsed = true;

		if (!mDLOutput && (!inputUsed || supportsFullDuplex))
		{
			if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&mDLOutput) != S_OK)
				mDLOutput = NULL;
		}

		SAFE_RELEASE(pDL);

		if (mDLOutput && mDLInput)
			break;
	}

	if (!mDLOutput || !mDLInput)
	{
		MessageBox(NULL, _T("Expected both Input and Output DeckLink devices"), _T("This application requires two DeckLink devices."), MB_OK);
		goto error;
	}

	if (mDLOutput->GetDisplayModeIterator(&pDLDisplayModeIterator) != S_OK)
	{
		MessageBox(NULL, _T("Cannot get Display Mode Iterator."), _T("DeckLink error."), MB_OK);
		goto error;
	}

	while (pDLDisplayModeIterator->Next(&pDLDisplayMode) == S_OK)
	{
		if (pDLDisplayMode->GetDisplayMode() == displayMode)
			break;

		SAFE_RELEASE(pDLDisplayMode);
	}
	SAFE_RELEASE(pDLDisplayModeIterator);

	if (pDLDisplayMode == NULL)
	{
		MessageBox(NULL, _T("Cannot get specified BMDDisplayMode."), _T("DeckLink error."), MB_OK);
		goto error;
	}

	mFrameWidth = pDLDisplayMode->GetWidth();
	mFrameHeight = pDLDisplayMode->GetHeight();

	if (!CheckFastTransferAvailable())
		goto error;

	if (!InitD3D())
		goto error;

	// Compute a rotate angle rate so box will spin at a rate independent of video mode frame rate
	pDLDisplayMode->GetFrameRate(&mFrameDuration, &mFrameTimescale);
	fps = (float)mFrameTimescale / (float)mFrameDuration;
	mRotateAngleRate = 35.0f / fps;			// rotate box through 35 degrees every second

	// Resize window to match video frame, but scale large formats down by half for viewing
	if (mFrameWidth < 1920)
		resizeWindow(mFrameWidth, mFrameHeight);
	else
		resizeWindow(mFrameWidth / 2, mFrameHeight / 2);

	if (mFastTransferExtensionAvailable)
	{
		// Initialize fast video frame transfers
		if (!VideoFrameTransfer::initialize(mpD3DDevice, mFrameWidth, mFrameHeight, (void *)mpCaptureTexture, (void *)mpPlayoutTexture))
		{
			MessageBox(NULL, _T("Cannot initialize video transfers."), _T("VideoFrameTransfer error."), MB_OK);
			goto error;
		}
	}

	// Capture will use a user-supplied frame memory allocator
	mCaptureAllocator = new PinnedMemoryAllocator(mpD3DDevice, VideoFrameTransfer::CPUtoGPU, 3);

	if (mDLInput->SetVideoInputFrameMemoryAllocator(mCaptureAllocator) != S_OK)
		goto error;

	if (mDLInput->EnableVideoInput(displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) != S_OK)
		goto error;

	mCaptureDelegate = new CaptureDelegate(this);
	if (mDLInput->SetCallback(mCaptureDelegate) != S_OK)
		goto error;

	// Playout will use a user-supplied frame memory allocator
	mPlayoutAllocator = new PinnedMemoryAllocator(mpD3DDevice, VideoFrameTransfer::GPUtoCPU, 1);

	if (mDLOutput->SetVideoOutputFrameMemoryAllocator(mPlayoutAllocator) != S_OK)
		goto error;

	if (mDLOutput->EnableVideoOutput(displayMode, bmdVideoOutputFlagDefault) != S_OK)
		goto error;

	// Create a queue of 10 IDeckLinkMutableVideoFrame objects to use for scheduling output video frames.
	// The ScheduledFrameCompleted() callback will immediately schedule a new frame using the next video frame from this queue.
	for (int i = 0; i < 10; i++)
	{
		// The frame read back from the GPU frame buffer and used for the playout video frame is in BGRA format.
		// The BGRA frame will be converted on playout to YCbCr either in hardware on most DeckLink cards or in software
		// within the DeckLink API for DeckLink devices without this hardware conversion.
		// If you want RGB 4:4:4 format to be played out "over the wire" in SDI, turn on the "Use 4:4:4 SDI" in the control
		// panel or turn on the bmdDeckLinkConfig444SDIVideoOutput flag using the IDeckLinkConfiguration interface.
		IDeckLinkMutableVideoFrame* outputFrame;
		if (mDLOutput->CreateVideoFrame(mFrameWidth, mFrameHeight, mFrameWidth * 4, bmdFormat8BitBGRA, bmdFrameFlagDefault, &outputFrame) != S_OK)
			goto error;

		mDLOutputVideoFrameQueue.push_back(outputFrame);
	}

	mPlayoutDelegate = new PlayoutDelegate(this);
	if (mPlayoutDelegate == NULL)
		goto error;

	if (mDLOutput->SetScheduledFrameCompletionCallback(mPlayoutDelegate) != S_OK)
		goto error;

	bSuccess = true;

error:

	SAFE_RELEASE(pDLDisplayMode);
	SAFE_RELEASE(pDLIterator);
	SAFE_RELEASE(pDL);

	if (!bSuccess)
	{
		SAFE_RELEASE(mDLInput);
		SAFE_RELEASE(mDLOutput);
	}

	return bSuccess;
}

void DX11Composite::Render()
{
	ID3D11ShaderResourceView*               pCaptureTextureRV = NULL;

	// Clear the render target
	float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; // red, green, blue, alpha
	mpImmediateContext->ClearRenderTargetView(mpPlayoutTextureRTV, ClearColor);

	// Clear the depth buffer to 1.0 (max depth)
	mpImmediateContext->ClearDepthStencilView(mpDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

	//
	// Update rendering context.
	//
	mpImmediateContext->VSSetConstantBuffers(0, 1, &mpCBViewCube);
	mpImmediateContext->VSSetConstantBuffers(1, 1, &mpCBPerspectiveProjection);
	mpImmediateContext->VSSetConstantBuffers(2, 1, &mpCBChangesEveryFrame);
	mpImmediateContext->PSSetConstantBuffers(2, 1, &mpCBChangesEveryFrame);
	mpImmediateContext->PSSetSamplers(0, 1, &mpSamplerLinear);

	UINT stride;
	UINT offset;

	// Create pixel shader resource view for capture video texture
	D3D11_SHADER_RESOURCE_VIEW_DESC rvdesc;
	ZeroMemory(&rvdesc, sizeof(rvdesc));

	rvdesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	rvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	rvdesc.Texture2D.MipLevels = 1;
	rvdesc.Texture2D.MostDetailedMip = 0;

	HRESULT hr = mpD3DDevice->CreateShaderResourceView(mpCaptureTexture, &rvdesc, &pCaptureTextureRV);
	if (FAILED(hr)) {
		printf("Error creating texture resourece view\n");
	}

	// Set pixel shader resource view
	mpImmediateContext->PSSetShaderResources(0, 1, &pCaptureTextureRV);

	// Rotate scene around the origin
	XMMATRIX world;
	XMVECTOR vec = { -1.0f, 1.0f, 1.0f };
	world = XMMatrixMultiply(XMMatrixScaling(mAspectRatio, 1.0f, 1.0f), XMMatrixRotationAxis(vec, XMConvertToRadians(mRotateAngle)));
	mRotateAngle -= mRotateAngleRate;

	//
	// Update world.
	//
	CBChangesEveryFrame cb;
	cb.mWorld = XMMatrixTranspose(world);
	mpImmediateContext->UpdateSubresource(mpCBChangesEveryFrame, 0, NULL, &cb, 0, 0);

	// Set the view transform
	mpImmediateContext->VSSetConstantBuffers(0, 1, &mpCBViewCube);

	// Set the perspective projection
	mpImmediateContext->VSSetConstantBuffers(1, 1, &mpCBPerspectiveProjection);

	//
	// Draw a colourful frame around the front face of the box
	// (provides a pleasing nesting effect when you connect the playout output to the capture input)
	//

	// Set vertex and fragment shaders
	mpImmediateContext->VSSetShader(mpFrameVertexShader, NULL, 0);
	mpImmediateContext->PSSetShader(mpFrameFragmentShader, NULL, 0);

	// Set frame vertex buffer
	stride = sizeof(SimpleVertex);
	offset = 0;
	mpImmediateContext->IASetVertexBuffers(0, 1, &mpFrameVertexBuffer, &stride, &offset);

	// Set frame index buffer
	mpImmediateContext->IASetIndexBuffer(mpFrameIndexBuffer, DXGI_FORMAT_R16_UINT, 0);

	// Set primitive topology
	mpImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Disable blending
	mpImmediateContext->OMSetBlendState(mpBlendStateNoBlend, 0, 0xffffffff);

	mpImmediateContext->DrawIndexed(12, 0, 0);
	mpImmediateContext->DrawIndexed(12, 12, 0);
	mpImmediateContext->DrawIndexed(12, 24, 0);
	mpImmediateContext->DrawIndexed(12, 36, 0);

	if (mHasNoInputSource)
	{
		// Draw a big X when no input is available on capture

		// Set vertex buffer
		stride = sizeof(SimpleVertex);
		offset = 0;
		mpImmediateContext->IASetVertexBuffers(0, 1, &mpXVertexBuffer, &stride, &offset);

		// Set frame index buffer
		mpImmediateContext->IASetIndexBuffer(mpXIndexBuffer, DXGI_FORMAT_R16_UINT, 0);

		// Set primitive topology
		mpImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Disable blending
		mpImmediateContext->OMSetBlendState(mpBlendStateNoBlend, 0, 0xffffffff);

		mpImmediateContext->DrawIndexed(12, 0, 0);
		mpImmediateContext->DrawIndexed(12, 12, 0);
	}
	else
	{
		// Draw spinning cube

		// Set vertex and fragment shaders
		mpImmediateContext->VSSetShader(mpCubeVertexShader, NULL, 0);
		mpImmediateContext->PSSetShader(mpCubeFragmentShader, NULL, 0);

		// Set cube vertex buffer
		stride = sizeof(SimpleVertex);
		offset = 0;
		mpImmediateContext->IASetVertexBuffers(0, 1, &mpCubeVertexBuffer, &stride, &offset);

		// Set cube index buffer
		mpImmediateContext->IASetIndexBuffer(mpCubeIndexBuffer, DXGI_FORMAT_R16_UINT, 0);

		// Set primitive topology
		mpImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Disable blending
		mpImmediateContext->OMSetBlendState(mpBlendStateNoBlend, 0, 0xffffffff);

		// Draw front face.
		mpImmediateContext->DrawIndexed(12, 60, 0);

		// Enable blending
		mpImmediateContext->OMSetBlendState(mpBlendStateAlphaBlend, 0, 0xffffffff);

		// Draw left face.
		mpImmediateContext->DrawIndexed(12, 36, 0);

		// Draw back face.
		mpImmediateContext->DrawIndexed(12, 48, 0);

		// Draw right face.
		mpImmediateContext->DrawIndexed(12, 24, 0);
	}

	// Release capture texture resource view
	SAFE_RELEASE(pCaptureTextureRV);
}

void DX11Composite::paintDX()
{
	//
	// Present our back buffer to our front buffer
	//
	mpSwapChain->Present(0, 0);
}

void DX11Composite::resizeDX(WORD width, WORD height)
{
	// We don't set the project or model matrices here since the window data is copied directly from
	// an off-screen FBO in paintGL().  Just save the width and height for use in paintGL().
	mViewWidth = width;
	mViewHeight = height;
}

void DX11Composite::resizeWindow(int width, int height)
{
	RECT r;
	if (GetWindowRect(mhWnd, &r))
	{
		SetWindowPos(mhWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
	}
}

bool DX11Composite::InitD3D()
{
	HRESULT hr = S_OK;

	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	UINT numDriverTypes = ARRAYSIZE(driverTypes);

	D3D_FEATURE_LEVEL featureLevels[] =
	{
		D3D_FEATURE_LEVEL_11_0
	};
	UINT numFeatureLevels = ARRAYSIZE(featureLevels);

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferCount = 1;
	sd.BufferDesc.Width = mFrameWidth;
	sd.BufferDesc.Height = mFrameHeight;
	sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = mhWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
	{
		mDriverType = driverTypes[driverTypeIndex];
		hr = D3D11CreateDeviceAndSwapChain(NULL, mDriverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
			D3D11_SDK_VERSION, &sd, &mpSwapChain, &mpD3DDevice, &mFeatureLevel, &mpImmediateContext);
		if (SUCCEEDED(hr))
			break;
	}
	if (FAILED(hr))
		return false;

	DXGI_OUTPUT_DESC outputDesc;
	IDXGIOutput *pDXGIOutput;
	mpSwapChain->GetContainingOutput(&pDXGIOutput);
	pDXGIOutput->GetDesc(&outputDesc);

	// Create a render target view
	hr = mpSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&mpBackBuffer);
	if (FAILED(hr))
		return false;

	hr = mpD3DDevice->CreateRenderTargetView(mpBackBuffer, NULL, &mpRenderTargetView);
	if (FAILED(hr))
		return false;

	// Create depth stencil texture
	D3D11_TEXTURE2D_DESC descDepth;
	ZeroMemory(&descDepth, sizeof(descDepth));
	descDepth.Width = mFrameWidth;
	descDepth.Height = mFrameHeight;
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	descDepth.Usage = D3D11_USAGE_DEFAULT;
	descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MiscFlags = 0;
	hr = mpD3DDevice->CreateTexture2D(&descDepth, NULL, &mpDepthStencil);
	if (FAILED(hr))
		return false;

	// Create the depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory(&descDSV, sizeof(descDSV));
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	hr = mpD3DDevice->CreateDepthStencilView(mpDepthStencil, &descDSV, &mpDepthStencilView);
	if (FAILED(hr))
		return false;

	// Set depth stencil state
	D3D11_DEPTH_STENCIL_DESC descDSS;
	ZeroMemory(&descDSS, sizeof(descDSS));
	descDSS.DepthEnable = true;  // Turn on depth testing
	descDSS.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
	ID3D11DepthStencilState *pDepthStencilState;
	hr = mpD3DDevice->CreateDepthStencilState(&descDSS, &pDepthStencilState);
	if (FAILED(hr))
		printf("CreateDepthStencilState failed\n");
	mpImmediateContext->OMSetDepthStencilState(pDepthStencilState, 1);
	SAFE_RELEASE(pDepthStencilState);

	// Setup the viewport
	D3D11_VIEWPORT vp;
	vp.Width = (FLOAT)mFrameWidth;
	vp.Height = (FLOAT)mFrameHeight;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	mpImmediateContext->RSSetViewports(1, &vp);

	// Create vertex shader for cube faces
	createCubeVertexShader();

	// Initialize fragment shader for cube faces
	createCubeFragmentShader();

	// Create vertex shader for frame
	createFrameVertexShader();

	// Initialize fragment shader for frame
	createFrameFragmentShader();

	// Set the input layout
	mpImmediateContext->IASetInputLayout(mpVertexLayout);

	// Create vertex buffer for spinning cube
	SimpleVertex cubevertices[] =
	{
		{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },

		{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },

		{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },

		{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },

		{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },

		{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
		{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
	};

	D3D11_BUFFER_DESC bd;
	ZeroMemory(&bd, sizeof(bd));
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(SimpleVertex) * 24;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = 0;
	D3D11_SUBRESOURCE_DATA InitData;
	ZeroMemory(&InitData, sizeof(InitData));
	InitData.pSysMem = cubevertices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpCubeVertexBuffer);
	if (FAILED(hr))
		return false;

	// Create index buffer
	WORD cubeindices[] =
	{
		3, 1, 0,
		2, 1, 3,

		0, 1, 3,
		3, 1, 2,

		6, 4, 5,
		7, 4, 6,

		5, 4, 6,
		6, 4, 7,

		11, 9, 8,
		10, 9, 11,

		8, 9, 11,
		11, 9, 10,

		14, 12, 13,
		15, 12, 14,

		13, 12, 14,
		14, 12, 15,

		19, 17, 16,
		18, 17, 19,

		16, 17, 19,
		19, 17, 18,

		22, 20, 21,
		23, 20, 22,

		21, 20, 22,
		22, 20, 23
	};

	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(WORD) * 72;
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.CPUAccessFlags = 0;
	InitData.pSysMem = cubeindices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpCubeIndexBuffer);
	if (FAILED(hr))
		return false;

	SimpleVertex framevertices[] =
	{
		{ XMFLOAT3(1.2f, 1.2f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(1.2f, -1.2f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(-1.2f, -1.2f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(-1.2f, 1.2f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(1.2f, 1.2f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 0.0f) },
	};

	ZeroMemory(&bd, sizeof(bd));
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(SimpleVertex) * 10;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = 0;
	ZeroMemory(&InitData, sizeof(InitData));
	InitData.pSysMem = framevertices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpFrameVertexBuffer);
	if (FAILED(hr))
		return false;

	// Create index buffer
	WORD frameindices[] =
	{
		0, 1, 2,
		2, 1, 3,

		2, 1, 0,
		3, 1, 2,

		2, 3, 4,
		4, 3, 5,

		4, 3, 2,
		5, 3, 4,

		4, 5, 6,
		6, 5, 7,

		6, 5, 4,
		7, 5, 6,

		6, 7, 8,
		8, 7, 9,

		8, 7, 6,
		9, 7, 8,
	};

	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(WORD) * 48;
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.CPUAccessFlags = 0;
	InitData.pSysMem = frameindices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpFrameIndexBuffer);
	if (FAILED(hr))
		return false;

	SimpleVertex xvertices[] =
	{
		{ XMFLOAT3(0.8f, 0.9f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(0.9f, 0.8f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(-0.8f, -0.9f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(-0.9f, -0.8f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(-0.8f, 0.9f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(-0.9f, 0.8f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 0.0f) },
		{ XMFLOAT3(0.8f, -0.9f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
		{ XMFLOAT3(0.9f, -0.8f, 1.0f), XMFLOAT2(0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 0.0f) },
	};

	ZeroMemory(&bd, sizeof(bd));
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(SimpleVertex) * 8;
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bd.CPUAccessFlags = 0;
	ZeroMemory(&InitData, sizeof(InitData));
	InitData.pSysMem = xvertices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpXVertexBuffer);
	if (FAILED(hr))
		return false;

	// Create index buffer
	WORD xindices[] =
	{
		0, 1, 2,
		2, 0, 3,

		2, 1, 0,
		3, 0, 2,

		4, 5, 6,
		6, 4, 7,

		6, 5, 4,
		7, 4, 6,
	};

	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(WORD) * 24;
	bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bd.CPUAccessFlags = 0;
	InitData.pSysMem = xindices;
	hr = mpD3DDevice->CreateBuffer(&bd, &InitData, &mpXIndexBuffer);
	if (FAILED(hr))
		return false;

	// Create the constant buffers
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(CBView);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	hr = mpD3DDevice->CreateBuffer(&bd, NULL, &mpCBViewCube);
	if (FAILED(hr))
		return false;

	bd.ByteWidth = sizeof(CBProjection);
	hr = mpD3DDevice->CreateBuffer(&bd, NULL, &mpCBPerspectiveProjection);
	if (FAILED(hr))
		return false;

	bd.ByteWidth = sizeof(CBChangesEveryFrame);
	hr = mpD3DDevice->CreateBuffer(&bd, NULL, &mpCBChangesEveryFrame);
	if (FAILED(hr))
		return false;

	// Initialize the world matrices
	XMMATRIX world;
	world = XMMatrixIdentity();

	// Initialize the view matrix for the spinning cube
	XMVECTOR Eye = XMVectorSet(0.0f, 0.0f, 4.0f, 0.0f);
	XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
	XMVECTOR Up = XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f);
	XMMATRIX view = XMMatrixLookAtLH(Eye, At, Up);
	CBView cbViewCube;
	cbViewCube.mView = XMMatrixTranspose(view);
	mpImmediateContext->UpdateSubresource(mpCBViewCube, 0, NULL, &cbViewCube, 0, 0);

	// Calculate aspect ratio
	mAspectRatio = (FLOAT)mFrameWidth / (FLOAT)mFrameHeight;

	// Initialize the perspective projection matrix used when drawing the cube
	CBProjection cbPerspectiveProjection;
	cbPerspectiveProjection.mProjection = XMMatrixTranspose(XMMatrixPerspectiveFovLH(45.0f, mAspectRatio, 0.1f, 100.0f));
	mpImmediateContext->UpdateSubresource(mpCBPerspectiveProjection, 0, NULL, &cbPerspectiveProjection, 0, 0);

	// Setup the textures which will hold the captured and playout video frame pixels
	D3D11_TEXTURE2D_DESC textureDesc;
	ZeroMemory(&textureDesc, sizeof(textureDesc));

	// The captured video is YCbCr 4:2:2 packed into a UYVY macropixel.  DXGI has no YCbCr format
	// so treat it as RGBA 4:4:4:4 by halving the width and using an RGBA internal format.
	textureDesc.Width = mFrameWidth / 2;
	textureDesc.Height = mFrameHeight;
	textureDesc.MipLevels = 1;
	textureDesc.ArraySize = 1;
	textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	textureDesc.SampleDesc.Count = 1;
	textureDesc.Usage = D3D11_USAGE_DEFAULT;
	textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	textureDesc.CPUAccessFlags = 0;
	textureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;

	// Create the capture texture
	hr = mpD3DDevice->CreateTexture2D(&textureDesc, NULL, &mpCaptureTexture);
	if (FAILED(hr))
	{
		printf("Error creating Texture\n");
		return false;
	}

	// The playout video is full width.
	textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	textureDesc.Width = mFrameWidth;

	// Create the playout texture
	hr = mpD3DDevice->CreateTexture2D(&textureDesc, NULL, &mpPlayoutTexture);
	if (FAILED(hr))
	{
		printf("Error creating Texture\n");
		return false;
	}

	// Create the render target view for the playout texture
	D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
	renderTargetViewDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	renderTargetViewDesc.Texture2D.MipSlice = 0;

	hr = mpD3DDevice->CreateRenderTargetView(mpPlayoutTexture, &renderTargetViewDesc, &mpPlayoutTextureRTV);
	if (FAILED(hr))
	{
		printf("Error creating render target view\n");
		return false;
	}

	// Set the render target to the play out texture 
	mpImmediateContext->OMSetRenderTargets(1, &mpPlayoutTextureRTV, mpDepthStencilView);

	// Disabled blending state
	D3D11_BLEND_DESC blendDesc;
	ZeroMemory(&blendDesc, sizeof(blendDesc));
	blendDesc.IndependentBlendEnable = FALSE;
	blendDesc.RenderTarget[0].BlendEnable = FALSE;
	blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
	hr = mpD3DDevice->CreateBlendState(&blendDesc, &mpBlendStateNoBlend);
	if (FAILED(hr))
	{
		printf("Error creating blend state\n");
		return false;
	}

	// Alpha blend state
	ZeroMemory(&blendDesc, sizeof(blendDesc));
	blendDesc.IndependentBlendEnable = FALSE;
	blendDesc.RenderTarget[0].BlendEnable = TRUE;
	blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
	blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;
	blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
	blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
	hr = mpD3DDevice->CreateBlendState(&blendDesc, &mpBlendStateAlphaBlend);
	if (FAILED(hr))
	{
		printf("Error creating blend state\n");
		return false;
	}

	return true;
}


void DX11Composite::CleanupD3D()
{
	if (mpImmediateContext) mpImmediateContext->ClearState();

	SAFE_RELEASE(mpBlendStateNoBlend);
	SAFE_RELEASE(mpBlendStateAlphaBlend);
	SAFE_RELEASE(mpCaptureTexture);
	SAFE_RELEASE(mpPlayoutTexture);
	SAFE_RELEASE(mpPlayoutTextureRTV);
	SAFE_RELEASE(mpCBPerspectiveProjection);
	SAFE_RELEASE(mpCBChangesEveryFrame);
	SAFE_RELEASE(mpCubeVertexBuffer);
	SAFE_RELEASE(mpCubeIndexBuffer);
	SAFE_RELEASE(mpFrameVertexBuffer);
	SAFE_RELEASE(mpFrameIndexBuffer);
	SAFE_RELEASE(mpXVertexBuffer);
	SAFE_RELEASE(mpXIndexBuffer);
	SAFE_RELEASE(mpVertexLayout);
	SAFE_RELEASE(mpSamplerLinear);
	SAFE_RELEASE(mpCubeVertexShader);
	SAFE_RELEASE(mpCubeFragmentShader);
	SAFE_RELEASE(mpFrameVertexShader);
	SAFE_RELEASE(mpFrameFragmentShader);
	SAFE_RELEASE(mpDepthStencil);
	SAFE_RELEASE(mpDepthStencilView);
	SAFE_RELEASE(mpRenderTargetView);
	SAFE_RELEASE(mpBackBuffer);
	SAFE_RELEASE(mpSwapChain);
	SAFE_RELEASE(mpImmediateContext);
	SAFE_RELEASE(mpD3DDevice);
}


//
// Update the captured video frame texture
//
void DX11Composite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
{
	mHasNoInputSource = hasNoInputSource;
	if (mHasNoInputSource)
		return;							// don't transfer texture when there's no input

	EnterCriticalSection(&pMutex);

	long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight();
	void* videoPixels;
	inputFrame->GetBytes(&videoPixels);

	if (mFastTransferExtensionAvailable)
	{
		if (!mCaptureAllocator->transferFrame(videoPixels, (void *)mpCaptureTexture))
			OutputDebugStringA("Capture: transferFrame() failed\n");
	}

	LeaveCriticalSection(&pMutex);
}

// Draw the captured video frame texture onto a box, rendering to the off-screen frame buffer.
// Read the rendered scene back from the frame buffer and schedule it for playout.
void DX11Composite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
{
	EnterCriticalSection(&pMutex);

	// Get the first frame from the queue
	IDeckLinkMutableVideoFrame* outputVideoFrame = mDLOutputVideoFrameQueue.front();
	mDLOutputVideoFrameQueue.push_back(outputVideoFrame);
	mDLOutputVideoFrameQueue.pop_front();

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

	long rowbytes = outputVideoFrame->GetRowBytes();
	long height = outputVideoFrame->GetHeight();
	long memSize = rowbytes * height;

	if (mFastTransferExtensionAvailable)
	{
		// Signal that we're about to draw using mCaptureTexture onto mPlayoutTexture
		mCaptureAllocator->waitAPI();
	}

	Render();

	if (mFastTransferExtensionAvailable)
	{
		// Finished with mCaptureTexture
		mCaptureAllocator->endAPI();

		if (!mPlayoutAllocator->transferFrame(pFrame, (void *)mpPlayoutTexture))
			OutputDebugStringA("Playback: transferFrame() failed\n");

		// Wait for transfer to system memory to complete
		mPlayoutAllocator->waitSyncComplete(pFrame);
	}

	// Copy contents of playout texture to back buffer for display
	mpImmediateContext->CopyResource(mpBackBuffer, mpPlayoutTexture);

	paintDX();

	// If the last completed frame was late or dropped, bump the scheduled time further into the future
	if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped)
		mTotalPlayoutFrames += 2;

	// Schedule the next frame for playout
	HRESULT hr = mDLOutput->ScheduleVideoFrame(outputVideoFrame, (mTotalPlayoutFrames * mFrameDuration), mFrameDuration, mFrameTimescale);
	if (SUCCEEDED(hr))
		mTotalPlayoutFrames++;

	if (mFastTransferExtensionAvailable)
	{
		mPlayoutAllocator->endSyncComplete(pFrame);
	}

	LeaveCriticalSection(&pMutex);
}

bool DX11Composite::Start()
{
	mTotalPlayoutFrames = 0;

	// Preroll frames
	for (unsigned i = 0; i < 5; i++)
	{
		// Take each video frame from the front of the queue and move it to the back
		IDeckLinkMutableVideoFrame* outputVideoFrame = mDLOutputVideoFrameQueue.front();
		mDLOutputVideoFrameQueue.push_back(outputVideoFrame);
		mDLOutputVideoFrameQueue.pop_front();

		// Start with a black frame for playout
		void*	pFrame;
		outputVideoFrame->GetBytes((void**)&pFrame);
		memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mFrameHeight);		// 0 is black in RGBA format

		if (mDLOutput->ScheduleVideoFrame(outputVideoFrame, (mTotalPlayoutFrames * mFrameDuration), mFrameDuration, mFrameTimescale) != S_OK)
			return false;

		mTotalPlayoutFrames++;
	}

	mDLInput->StartStreams();
	mDLOutput->StartScheduledPlayback(0, mFrameTimescale, 1.0);

	return true;
}

bool DX11Composite::Stop()
{

	mDLInput->StopStreams();
	mDLInput->DisableVideoInput();

	mDLOutput->StopScheduledPlayback(0, NULL, 0);
	mDLOutput->DisableVideoOutput();

	return true;
}

bool DX11Composite::createCubeVertexShader()
{
	HRESULT hr = S_OK;

	// Create the vertex shader
	hr = mpD3DDevice->CreateVertexShader(g_cube_VS, sizeof(g_cube_VS), NULL, &mpCubeVertexShader);
	if (FAILED(hr))
		return false;

	// Define the input layout
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
	UINT numElements = ARRAYSIZE(layout);

	// Create the input layout
	hr = mpD3DDevice->CreateInputLayout(layout, numElements, g_cube_VS,
		sizeof(g_cube_VS), &mpVertexLayout);
	if (FAILED(hr))
		return false;

	return true;
}

// Setup fragment shader to take YCbCr 4:2:2 video texture in UYVY macropixel format
// and perform colour space conversion to RGBA in the GPU.
bool DX11Composite::createCubeFragmentShader()
{
	HRESULT hr = S_OK;

	// Create the pixel shader
	hr = mpD3DDevice->CreatePixelShader(g_cube_PS, sizeof(g_cube_PS), NULL, &mpCubeFragmentShader);
	if (FAILED(hr))
		return false;

	// Create pixel shader sampler 
	D3D11_SAMPLER_DESC samplerDesc;
	ZeroMemory(&samplerDesc, sizeof(samplerDesc));
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = 0;
	hr = mpD3DDevice->CreateSamplerState(&samplerDesc, &mpSamplerLinear);
	if (FAILED(hr))
		return false;

	return true;
}

bool DX11Composite::createFrameVertexShader()
{
	HRESULT hr = S_OK;

	// Create the vertex shader
	hr = mpD3DDevice->CreateVertexShader(g_frame_VS, sizeof(g_frame_VS), NULL, &mpFrameVertexShader);
	if (FAILED(hr))
		return false;

	// Define the input layout
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
	UINT numElements = ARRAYSIZE(layout);

	// Create the input layout
	hr = mpD3DDevice->CreateInputLayout(layout, numElements, g_frame_VS,
		sizeof(g_frame_VS), &mpVertexLayout);
	if (FAILED(hr))
		return false;

	return true;
}

// Setup fragment shader to take YCbCr 4:2:2 video texture in UYVY macropixel format
// and perform colour space conversion to RGBA in the GPU.
bool DX11Composite::createFrameFragmentShader()
{
	HRESULT hr = S_OK;

	// Create the pixel shader
	hr = mpD3DDevice->CreatePixelShader(g_frame_PS, sizeof(g_frame_PS), NULL, &mpFrameFragmentShader);
	if (FAILED(hr))
		return false;

	// Create pixel shader sampler 
	D3D11_SAMPLER_DESC samplerDesc;
	ZeroMemory(&samplerDesc, sizeof(samplerDesc));
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = 0;
	hr = mpD3DDevice->CreateSamplerState(&samplerDesc, &mpSamplerLinear);
	if (FAILED(hr))
		return false;

	return true;
}

bool DX11Composite::CheckFastTransferAvailable()
{
	mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable();

	if (!mFastTransferExtensionAvailable) {
		OutputDebugStringA("Fast memory transfer extension not available.\n");
		return false;
	}

	return true;
}

////////////////////////////////////////////
// PinnedMemoryAllocator
////////////////////////////////////////////

// PinnedMemoryAllocator implements the IDeckLinkMemoryAllocator interface and can be used instead of the
// built-in frame allocator, by setting with SetVideoInputFrameMemoryAllocator() or SetVideoOutputFrameMemoryAllocator().
//
// If the pinned memory extension is not available, this allocator will still be used and
// demonstrates how to cache frame allocations for efficiency.
//
// The frame cache delays the releasing of buffers until the cache fills up, thereby avoiding an
// allocate plus pin operation for every frame, followed by an unpin and deallocate on every frame.

PinnedMemoryAllocator::PinnedMemoryAllocator(ID3D11Device* pD3DDevice, VideoFrameTransfer::Direction direction, unsigned cacheSize) :
	mpD3DDevice(pD3DDevice),
	mRefCount(1),
	mDirection(direction),
	mFrameCacheSize(cacheSize)		// large cache size will keep more memory pinned and may result in out of memory errors
{
}

PinnedMemoryAllocator::~PinnedMemoryAllocator()
{
}

bool PinnedMemoryAllocator::transferFrame(void* address, void* gpuTexture)
{
	// Catch attempt to pin and transfer memory we didn't allocate
	if (mAllocatedSize.count(address) == 0)
		return false;

	if (mFrameTransfer.count(address) == 0)
	{
		// VideoFrameTransfer prepares and pins address
		mFrameTransfer[address] = new VideoFrameTransfer(mpD3DDevice, mAllocatedSize[address], address, mDirection);
	}

	return mFrameTransfer[address]->performFrameTransfer();
}

void PinnedMemoryAllocator::waitSyncComplete(void* address)
{
	if (mAllocatedSize.count(address) && mFrameTransfer.count(address))
		mFrameTransfer[address]->waitSyncComplete();
}

void PinnedMemoryAllocator::endSyncComplete(void* address)
{
	if (mAllocatedSize.count(address) && mFrameTransfer.count(address))
		mFrameTransfer[address]->endSyncComplete();
}

void PinnedMemoryAllocator::waitAPI()
{
	VideoFrameTransfer::waitAPI(mDirection);
}

void PinnedMemoryAllocator::endAPI()
{
	VideoFrameTransfer::endAPI(mDirection);
}

void PinnedMemoryAllocator::unPinAddress(void* address)
{
	// un-pin address only if it has been pinned for transfer
	if (mFrameTransfer.count(address) > 0)
	{
		mFrameTransfer.erase(address);
	}
}

// IUnknown methods
HRESULT STDMETHODCALLTYPE	PinnedMemoryAllocator::QueryInterface(REFIID /*iid*/, LPVOID* /*ppv*/)
{
	return E_NOTIMPL;
}

ULONG STDMETHODCALLTYPE		PinnedMemoryAllocator::AddRef(void)
{
	return InterlockedIncrement(&mRefCount);
}

ULONG STDMETHODCALLTYPE		PinnedMemoryAllocator::Release(void)
{
	int newCount = InterlockedDecrement(&mRefCount);
	if (newCount == 0)
		delete this;
	return newCount;
}

// IDeckLinkMemoryAllocator methods
HRESULT STDMETHODCALLTYPE	PinnedMemoryAllocator::AllocateBuffer(unsigned int bufferSize, void* *allocatedBuffer)
{
	if (mFrameCache.empty())
	{
		// Allocate memory on a page boundary
		*allocatedBuffer = VirtualAlloc(NULL, bufferSize, MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH, PAGE_READWRITE);

		if (!*allocatedBuffer)
			return E_OUTOFMEMORY;

		mAllocatedSize[*allocatedBuffer] = bufferSize;
	}
	else
	{
		// Re-use most recently ReleaseBuffer'd address
		*allocatedBuffer = mFrameCache.back();
		mFrameCache.pop_back();
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE	PinnedMemoryAllocator::ReleaseBuffer(void* buffer)
{
	if (mFrameCache.size() < mFrameCacheSize)
	{
		mFrameCache.push_back(buffer);
	}
	else
	{
		// No room left in cache, so un-pin (if it was pinned) and free this buffer
		unPinAddress(buffer);
		VirtualFree(buffer, 0, MEM_RELEASE);

		mAllocatedSize.erase(buffer);
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE	PinnedMemoryAllocator::Commit()
{
	return S_OK;
}

HRESULT STDMETHODCALLTYPE	PinnedMemoryAllocator::Decommit()
{
	while (!mFrameCache.empty())
	{
		// Cleanup any frames allocated and pinned in AllocateBuffer() but not freed in ReleaseBuffer()
		unPinAddress(mFrameCache.back());
		VirtualFree(mFrameCache.back(), 0, MEM_RELEASE);
		mFrameCache.pop_back();
	}
	return S_OK;
}

////////////////////////////////////////////
// DeckLink Capture Delegate Class
////////////////////////////////////////////
CaptureDelegate::CaptureDelegate(DX11Composite* pOwner) : 
	m_pOwner(pOwner),
	mRefCount(1)
{
}

HRESULT	CaptureDelegate::QueryInterface(REFIID iid, LPVOID *ppv)
{
	return E_NOTIMPL;
}
ULONG	CaptureDelegate::AddRef()
{
	return InterlockedIncrement(&mRefCount);
}
ULONG	CaptureDelegate::Release()
{
	int newCount = InterlockedDecrement(&mRefCount);
	if (newCount == 0)
		delete this;
	return newCount;
}

HRESULT	CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket* /*audioPacket*/)
{
	if (!inputFrame)
	{
		// It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame.
		return S_OK;
	}

	bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;

	m_pOwner->VideoFrameArrived(inputFrame, hasNoInputSource);
	return S_OK;
}

HRESULT	CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags)
{
	return S_OK;
}

////////////////////////////////////////////
// DeckLink Playout Delegate Class
////////////////////////////////////////////
PlayoutDelegate::PlayoutDelegate(DX11Composite* pOwner) : 
	m_pOwner(pOwner),
	mRefCount(1)
{
}

HRESULT	PlayoutDelegate::QueryInterface(REFIID iid, LPVOID *ppv)
{
	return E_NOTIMPL;
}
ULONG	PlayoutDelegate::AddRef()
{
	return InterlockedIncrement(&mRefCount);
}
ULONG	PlayoutDelegate::Release()
{
	int newCount = InterlockedDecrement(&mRefCount);
	if (newCount == 0)
		delete this;
	return newCount;
}

HRESULT	PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
	switch (result)
	{
		case bmdOutputFrameDisplayedLate:
			OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Frame Displayed Late\n");
			break;
		case bmdOutputFrameDropped:
			OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Frame Dropped\n");
			break;
		case bmdOutputFrameCompleted:
		case bmdOutputFrameFlushed:
			// Don't log bmdOutputFrameFlushed result since it is expected when Stop() is called
			break;
		default:
			OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
	}

	m_pOwner->PlayoutFrameCompleted(completedFrame, result);
	return S_OK;
}

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