// DirectX9 Enhanced video output
#include "burner.h"
#include "vid_directx_support.h"

// #define ENABLE_PROFILING FBA_DEBUG
// #define LOAD_EFFECT_FROM_FILE

#define DIRECT3D_VERSION 0x0900							// Use this Direct3D version
#define D3D_OVERLOADS
#include <d3d9.h>
#include <d3dx9effect.h>

const float PI = 3.14159265358979323846f;

typedef struct _D3DLVERTEX2 {
	union { FLOAT x;  FLOAT dvX; };
	union { FLOAT y;  FLOAT dvY; };
	union { FLOAT z;  FLOAT dvZ; };
	union { D3DCOLOR color; D3DCOLOR dcColor; };
	union { D3DCOLOR specular; D3DCOLOR dcSpecular; };
	union { FLOAT tu;  FLOAT dvTU; };
	union { FLOAT tv;  FLOAT dvTV; };
	union { FLOAT tu1; FLOAT dvTU1; };
	union { FLOAT tv1; FLOAT dvTV1; };
#if(DIRECT3D_VERSION >= 0x0500)
 #if (defined __cplusplus) && (defined D3D_OVERLOADS)
	_D3DLVERTEX2() { }
	_D3DLVERTEX2(FLOAT _x, FLOAT _y, FLOAT _z, D3DCOLOR _color, D3DCOLOR _specular, FLOAT _tu, FLOAT _tv, FLOAT _tu1, FLOAT _tv1)
	{
		x = _x; y = _y; z = _z;
		color = _color; specular = _specular;
		tu = _tu; tv = _tv;
		tu1 = _tu1; tv1 = _tv1;
	}
 #endif
#endif
} D3DLVERTEX2, *LPD3DLVERTEX2;

#define D3DFVF_LVERTEX2 ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX2 | D3DFVF_TEXCOORDSIZE2(0) | D3DFVF_TEXCOORDSIZE2(1) | D3DFVF_TEXCOORDSIZE2(2) | D3DFVF_TEXCOORDSIZE2(3) )

#define RENDER_STRIPS (4)

#define DX9_SHADERPRECISION  ((nVidBlitterOpt[nVidSelect] & (7 << 28)) >> 28)
#define DX9_FILTER           ((nVidBlitterOpt[nVidSelect] & (3 << 24)) >> 24)
#define DX9_USE_PS20		 ( nVidBlitterOpt[nVidSelect] & (1 <<  9))
#define DX9_USE_FPTEXTURE    ( nVidBlitterOpt[nVidSelect] & (1 <<  8))

static IDirect3D9* pD3D = NULL;							// Direct3D interface
static D3DPRESENT_PARAMETERS d3dpp;
static IDirect3DDevice9* pD3DDevice = NULL;
static IDirect3DVertexBuffer9* pVB[RENDER_STRIPS] = { NULL, };
static IDirect3DTexture9* pTexture = NULL;
static IDirect3DTexture9* pScanlineTexture[2] = { NULL, };
static int nTextureWidth, nTextureHeight;
static IDirect3DSurface9* pSurface = NULL;

static IDirect3DVertexBuffer9* pIntermediateVB = NULL;
static IDirect3DTexture9* pIntermediateTexture = NULL;
static int nIntermediateTextureWidth, nIntermediateTextureHeight;

static ID3DXEffect* pEffect = NULL;
static ID3DXTextureShader* pEffectShader = NULL;
static D3DXHANDLE hTechnique = NULL;
static D3DXHANDLE hScanIntensity = NULL;
static IDirect3DTexture9* pEffectTexture = NULL;

static double dPrevCubicB, dPrevCubicC;

static bool bUsePS14;

static int nGameWidth = 0, nGameHeight = 0;				// Game screen size
static int nGameImageWidth, nGameImageHeight;

static int nRotateGame;
static int nImageWidth, nImageHeight, nImageZoom;

static RECT Dest;

// ----------------------------------------------------------------------------

static TCHAR* TextureFormatString(D3DFORMAT nFormat)
{
	switch (nFormat) {
		case D3DFMT_X1R5G5B5:
			return _T("16-bit xRGB 1555");
		case D3DFMT_R5G6B5:
			return _T("16-bit RGB 565");
		case D3DFMT_X8R8G8B8:
			return _T("32-bit xRGB 8888");
		case D3DFMT_A8R8G8B8:
			return _T("32-bit ARGB 8888");
		case D3DFMT_A16B16G16R16F:
			return _T("64-bit ARGB 16161616fp");
		case D3DFMT_A32B32G32R32F:
			return _T("128-bit ARGB 32323232fp");
	}

	return _T("unknown format");
}

// ----------------------------------------------------------------------------

static int GetTextureSize(int nSize)
{
	int nTextureSize = 128;

	while (nTextureSize < nSize) {
		nTextureSize <<= 1;
	}

	return nTextureSize;
}

static void PutPixel(unsigned char** ppSurface, unsigned int nColour)
{
	switch (nVidScrnDepth) {
		case 15:
			*((unsigned short*)(*ppSurface)) = ((nColour >> 9) & 0x7C00) | ((nColour >> 6) & 0x03E0) | ((nColour >> 3) & 0x001F);
			*ppSurface += 2;
			break;
		case 16:
			*((unsigned short*)(*ppSurface)) = ((nColour >> 8) & 0xF800) | ((nColour >> 5) & 0x07E0) | ((nColour >> 3) & 0x001F);
			*ppSurface += 2;
			break;
		case 24:
			(*ppSurface)[0] = (nColour >> 16) & 0xFF;
			(*ppSurface)[1] = (nColour >>  8) & 0xFF;
			(*ppSurface)[2] = (nColour >>  0) & 0xFF;
			*ppSurface += 3;
			break;
		case 32:
			*((unsigned int*)(*ppSurface)) = nColour;
			*ppSurface += 4;
			break;
	}
}

// Select optimal full-screen resolution
int dx9SelectFullscreenMode(VidSDisplayScoreInfo* pScoreInfo)
{
	int nWidth, nHeight;

	if (bVidArcaderes) {
		if (!VidSGetArcaderes((int*)&(pScoreInfo->nBestWidth), (int*)&(pScoreInfo->nBestHeight))) {
			return 1;
		}
	} else {
		if (nScreenSize) {
			D3DFORMAT nFormat = (nVidDepth == 16) ? D3DFMT_R5G6B5 : D3DFMT_X8R8G8B8;
			D3DDISPLAYMODE dm;

			memset(pScoreInfo, 0, sizeof(VidSDisplayScoreInfo));
			pScoreInfo->nRequestedZoom = nScreenSize;
			VidSInitScoreInfo(pScoreInfo);

			// Enumerate the available screenmodes
			for (int i = pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT, nFormat) - 1; i >= 0; i--) {
				if (FAILED(pD3D->EnumAdapterModes(D3DADAPTER_DEFAULT, nFormat, i, &dm))) {
					return 1;
				}
				pScoreInfo->nModeWidth = dm.Width;
				pScoreInfo->nModeHeight = dm.Height;

				VidSScoreDisplayMode(pScoreInfo);
			}

			if (pScoreInfo->nBestWidth == -1U) {

				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_UI_FULL_NOMODE));
				FBAPopupDisplay(PUF_TYPE_ERROR);

				return 1;
			}

		} else {
			pScoreInfo->nBestWidth = nVidWidth;
			pScoreInfo->nBestHeight = nVidHeight;
		}
	}

	if (!bDrvOkay && (pScoreInfo->nBestWidth < 640 || pScoreInfo->nBestHeight < 480)) {
		return 1;
	}

	return 0;
}

// ----------------------------------------------------------------------------

static void FillEffectTexture()
{
	FLOAT B = (FLOAT)dVidCubicB;
	FLOAT C = (FLOAT)dVidCubicC;

	if (pEffect == NULL) {
		return;
	}

/*
	if (dVidCubicSharpness > 0.5) {
		C = dVidCubicSharpness;
		B = 0.0;
	} else {
		C = dVidCubicSharpness;
		B = 1.0 - 2.0 * C;
	}
*/

	if (DX9_SHADERPRECISION == 4 || bUsePS14) {
		B = 0.0;
	}

	if (pEffectShader && pEffectTexture) {
		pEffectShader->SetFloat("B", B);
		pEffectShader->SetFloat("C", C);

		D3DXFillTextureTX(pEffectTexture, pEffectShader);
	}

	pEffect->SetFloat("B", B);
	pEffect->SetFloat("C", C);
}

// ----------------------------------------------------------------------------

static void dx9ReleaseResources()
{
	RELEASE(pEffect);
	RELEASE(pEffectShader);
	RELEASE(pEffectTexture);

	RELEASE(pScanlineTexture[0]);
	RELEASE(pScanlineTexture[1]);
	RELEASE(pIntermediateTexture);

	RELEASE(pSurface);
	RELEASE(pTexture);

	for (int y = 0; y < RENDER_STRIPS; y++) {
		RELEASE(pVB[y]);
	}
	RELEASE(pIntermediateVB);
}

static int dx9Exit()
{
	dx9ReleaseResources();

	VidSFreeVidImage();

	RELEASE(pD3DDevice);
	RELEASE(pD3D);

	nRotateGame = 0;

	return 0;
}

static int dx9SurfaceInit()
{
	D3DFORMAT nFormat;

	if (nRotateGame & 1) {
		nVidImageWidth = nGameHeight;
		nVidImageHeight = nGameWidth;
	} else {
		nVidImageWidth = nGameWidth;
		nVidImageHeight = nGameHeight;
	}

	nGameImageWidth = nVidImageWidth;
	nGameImageHeight = nVidImageHeight;

	nVidImageDepth = bDrvOkay ? 15 : 32;

	switch (nVidImageDepth) {
		case 32:
			nFormat = D3DFMT_X8R8G8B8;
			break;
		case 24:
			nFormat = D3DFMT_R8G8B8;
			break;
		case 16:
			nFormat = D3DFMT_R5G6B5;
			break;
		case 15:
			nFormat = D3DFMT_X1R5G5B5;
			break;
	}

	nVidImageBPP = (nVidImageDepth + 7) >> 3;
	nBurnBpp = nVidImageBPP;								// Set Burn library Bytes per pixel

	// Use our callback to get colors:
	SetBurnHighCol(nVidImageDepth);

	// Make the normal memory buffer
	if (VidSAllocVidImage()) {
		dx9Exit();
		return 1;
	}

	if (FAILED(pD3DDevice->CreateOffscreenPlainSurface(nVidImageWidth, nVidImageHeight, nFormat, D3DPOOL_DEFAULT, &pSurface, NULL))) {
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't create surface.\n"));
#endif
		return 1;
	}
#ifdef PRINT_DEBUG_INFO
   	dprintf(_T("  * Allocated a %i x %i (%s) surface.\n"), nVidImageWidth, nVidImageHeight, TextureFormatString(nFormat));
#endif

	nTextureWidth = GetTextureSize(nGameImageWidth);
	nTextureHeight = GetTextureSize(nGameImageHeight);

	if (FAILED(pD3DDevice->CreateTexture(nTextureWidth, nTextureHeight, 1, D3DUSAGE_RENDERTARGET, nFormat, D3DPOOL_DEFAULT, &pTexture, NULL))) {
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't create texture.\n"));
#endif
		return 1;
	}
#ifdef PRINT_DEBUG_INFO
   	dprintf(_T("  * Allocated a %i x %i (%s) image texture.\n"), nTextureWidth, nTextureHeight, TextureFormatString(nFormat));
#endif

	return 0;
}

static int dx9EffectSurfaceInit()
{
	D3DFORMAT nFormat;

	if (DX9_FILTER == 2 && (bUsePS14 || (DX9_SHADERPRECISION != 0 && DX9_SHADERPRECISION != 2))) {
		switch (DX9_SHADERPRECISION) {
			case 0:
				nFormat = D3DFMT_A32B32G32R32F;
				break;
			case 1:
				nFormat = D3DFMT_A16B16G16R16F;
				break;
			case 2:
				nFormat = D3DFMT_A32B32G32R32F;
				break;
			case 3:
				nFormat = D3DFMT_A16B16G16R16F;
				break;
			default:
				nFormat = D3DFMT_A8R8G8B8;
		}

		if (bUsePS14) {
			nFormat = D3DFMT_A8R8G8B8;
		}

		if (FAILED(pD3DDevice->CreateTexture(1024, 1, 1, D3DUSAGE_RENDERTARGET, nFormat, D3DPOOL_DEFAULT, &pEffectTexture, NULL))) {
#ifdef PRINT_DEBUG_INFO
   			dprintf(_T("  * Error: Couldn't create effects texture.\n"));
#endif
			return 1;
		}
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Allocated a %i x %i (%s) LUT texture.\n"), 1024, 1, TextureFormatString(nFormat));
#endif
	}

	if ((DX9_FILTER == 1 && bVidScanlines) || (DX9_FILTER == 2 && (bUsePS14 || DX9_SHADERPRECISION >= 2 || bVidScanlines))) {
		if (nVidFullscreen) {
			nIntermediateTextureWidth = GetTextureSize(nRotateGame ? nVidScrnHeight : nVidScrnWidth);
		} else {
			nIntermediateTextureWidth = GetTextureSize(nRotateGame ? SystemWorkArea.bottom - SystemWorkArea.top : SystemWorkArea.right - SystemWorkArea.left);
		}
		nIntermediateTextureHeight = nTextureHeight;

		nFormat = D3DFMT_A8R8G8B8;
		if ((DX9_FILTER == 2) && !bUsePS14 && DX9_USE_FPTEXTURE) {
#if 0
			if ((DX9_SHADERPRECISION == 0 || DX9_SHADERPRECISION == 2) && !bVidScanlines) {
				nFormat = D3DFMT_A32B32G32R32F;
			} else {
				nFormat = D3DFMT_A16B16G16R16F;
			}
#else
			nFormat = D3DFMT_A16B16G16R16F;
#endif
		}

		if (FAILED(pD3DDevice->CreateTexture(nIntermediateTextureWidth, nIntermediateTextureHeight, 1, D3DUSAGE_RENDERTARGET, nFormat, D3DPOOL_DEFAULT, &pIntermediateTexture, NULL))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't create intermediate texture.\n"));
#endif
			return 1;
		}
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Allocated a %i x %i (%s) intermediate texture.\n"), nIntermediateTextureWidth, nIntermediateTextureHeight, TextureFormatString(nFormat));
#endif
	}

	{
		// Clear textures and set border colour

		IDirect3DSurface9* pSurf;

		pTexture->GetSurfaceLevel(0, &pSurf);
		pD3DDevice->ColorFill(pSurf, 0, D3DCOLOR_XRGB(0, 0, 0));
		RELEASE(pSurf);

		if (pIntermediateTexture) {
			pIntermediateTexture->GetSurfaceLevel(0, &pSurf);
			pD3DDevice->ColorFill(pSurf, 0, D3DCOLOR_XRGB(0, 0, 0));
			RELEASE(pSurf);
		}

		pD3DDevice->SetSamplerState(0, D3DSAMP_BORDERCOLOR, D3DCOLOR_XRGB(0, 0, 0));
		pD3DDevice->SetSamplerState(1, D3DSAMP_BORDERCOLOR, D3DCOLOR_XRGB(0, 0, 0));
	}

	// Allocate scanline textures
	for (int i = 0, nSize = 2; i < 2; i++, nSize <<= 1) {
		RECT rect = { 0, 0, nSize, nSize };
		IDirect3DSurface9* pSurf = NULL;

		unsigned int scan2x2[] =  { 0xFFFFFF, 0xFFFFFF,
									0x000000, 0x000000 };

		unsigned int scan4x4[] =  { 0x9F9F9F, 0x9F9F9F, 0x9F9F9F, 0x9F9F9F,
									0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF,
									0x9F9F9F, 0x9F9F9F, 0x9F9F9F, 0x9F9F9F,
									0x000000, 0x000000, 0x000000, 0x000000 };

		unsigned int* scandata[] = { scan2x2, scan4x4 };

		if (FAILED(pD3DDevice->CreateTexture(nSize, nSize, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &(pScanlineTexture[i]), NULL))) {
#ifdef PRINT_DEBUG_INFO
	   		dprintf(_T("  * Error: Couldn't create texture.\n"));
#endif
			return 1;
		}
		if (FAILED(pScanlineTexture[i]->GetSurfaceLevel(0, &pSurf))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't get texture surface.\n"));
#endif
		}

		if (FAILED(D3DXLoadSurfaceFromMemory(pSurf, NULL, &rect, scandata[i], D3DFMT_X8R8G8B8, nSize * sizeof(int), NULL, &rect, D3DX_FILTER_NONE, 0))) {
#ifdef PRINT_DEBUG_INFO
	   		dprintf(_T("  * Error: D3DXLoadSurfaceFromMemory failed.\n"));
#endif
		}

		RELEASE(pSurf);
	}

	return 0;
}

int dx9GeometryInit()
{
	for (int y = 0; y < RENDER_STRIPS; y++) {

		D3DLVERTEX2 vScreen[4];

		// The polygons for the emulator image on the screen
		{
			int nWidth =     pIntermediateTexture ? nImageWidth : nGameImageWidth;
			int nHeight =    nGameImageHeight;
			int nTexWidth =  pIntermediateTexture ? nIntermediateTextureWidth : nTextureWidth;
			int nTexHeight = nTextureHeight;

			// Add 0.0000001 to ensure consistent rounding
			double dTexCoordXl =  																			0.0000001;
			double dTexCoordXr = (double)nWidth					   / (double)nTexWidth                    + 0.0000001;
			double dTexCoordYt = (double)nHeight * (double)(y + 0) / (double)(nTexHeight * RENDER_STRIPS) + 0.0000001;
			double dTexCoordYb = (double)nHeight * (double)(y + 1) / (double)(nTexHeight * RENDER_STRIPS) + 0.0000001;

			double dLeft   = 1.0 - 2.0;
			double dRight  = 1.0 - 0.0;
			double dTop    = 1.0 - (((double)((nRotateGame & 1) ? (RENDER_STRIPS - y - 0) : (y + 0))) * 2.0 / RENDER_STRIPS);
			double dBottom = 1.0 - (((double)((nRotateGame & 1) ? (RENDER_STRIPS - y - 1) : (y + 1))) * 2.0 / RENDER_STRIPS);

			if (pIntermediateTexture && bVidScanlines) {
				dTexCoordXl += 0.5 / (double)nIntermediateTextureWidth;
				dTexCoordXr += 0.5 / (double)nIntermediateTextureWidth;
				dTexCoordYt += 1.0 / (double)nIntermediateTextureHeight;
				dTexCoordYb += 1.0 / (double)nIntermediateTextureHeight;
			}


#if 0
			if (nPreScaleEffect) {
				if (nPreScale & 1) {
					nWidth *= nPreScaleZoom;
					nTexWidth = nPreScaleTextureWidth;
				}
				if (nPreScale & 2) {
					nHeight *= nPreScaleZoom;
					nTexHeight = nPreScaleTextureHeight;
				}
			}
#endif

			// Set up the vertices for the game image, including the texture coordinates for the game image only
			if (nRotateGame & 1) {
				vScreen[(nRotateGame & 2) ? 3 : 0] = D3DLVERTEX2(dTop,    dLeft,  0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYt, 0, 0);
				vScreen[(nRotateGame & 2) ? 2 : 1] = D3DLVERTEX2(dTop,    dRight, 0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYt, 0, 0);
				vScreen[(nRotateGame & 2) ? 1 : 2] = D3DLVERTEX2(dBottom, dLeft,  0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYb, 0, 0);
				vScreen[(nRotateGame & 2) ? 0 : 3] = D3DLVERTEX2(dBottom, dRight, 0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYb, 0, 0);
			} else {
				vScreen[(nRotateGame & 2) ? 3 : 0] = D3DLVERTEX2(dLeft,  dTop,    0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYt, 0, 0);
				vScreen[(nRotateGame & 2) ? 2 : 1] = D3DLVERTEX2(dRight, dTop,    0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYt, 0, 0);
				vScreen[(nRotateGame & 2) ? 1 : 2] = D3DLVERTEX2(dLeft,  dBottom, 0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYb, 0, 0);
				vScreen[(nRotateGame & 2) ? 0 : 3] = D3DLVERTEX2(dRight, dBottom, 0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYb, 0, 0);
			}

			{
				// Set up the 2nd pair of texture coordinates, used for scanlines or the high performance version of the cubic filter

				double dScanOffset = nGameImageHeight / -2.0;

				if (bVidScanlines) {
					int s, z = nImageHeight / nGameImageHeight;

					for (s = 2; s < z; s <<= 1) { }
					dScanOffset += 0.5 / s;
				} else {
					dScanOffset += 0.5;
				}

				// Set the texture coordinates for the scanlines
				if (nRotateGame & 1) {
					vScreen[nRotateGame & 2 ? 3 : 0].tu1 = dScanOffset + 0.0;					vScreen[nRotateGame & 2 ? 3 : 0].tv1 = dScanOffset + 0.0								+ 0.0000001;
					vScreen[nRotateGame & 2 ? 2 : 1].tu1 = dScanOffset + (double)nGameHeight;	vScreen[nRotateGame & 2 ? 2 : 1].tv1 = dScanOffset + 0.0								+ 0.0000001;
					vScreen[nRotateGame & 2 ? 1 : 2].tu1 = dScanOffset + 0.0;					vScreen[nRotateGame & 2 ? 1 : 2].tv1 = dScanOffset + (double)nGameWidth / RENDER_STRIPS + 0.0000001;
					vScreen[nRotateGame & 2 ? 0 : 3].tu1 = dScanOffset + (double)nGameHeight;	vScreen[nRotateGame & 2 ? 0 : 3].tv1 = dScanOffset + (double)nGameWidth / RENDER_STRIPS + 0.0000001;
				} else {
					vScreen[nRotateGame & 2 ? 3 : 0].tu1 = dScanOffset + 0.0;					vScreen[nRotateGame & 2 ? 3 : 0].tv1 = dScanOffset + 0.0								 + 0.0000001;
					vScreen[nRotateGame & 2 ? 2 : 1].tu1 = dScanOffset + (double)nGameWidth;	vScreen[nRotateGame & 2 ? 2 : 1].tv1 = dScanOffset + 0.0								 + 0.0000001;
					vScreen[nRotateGame & 2 ? 1 : 2].tu1 = dScanOffset + 0.0;					vScreen[nRotateGame & 2 ? 1 : 2].tv1 = dScanOffset + (double)nGameHeight / RENDER_STRIPS + 0.0000001;
					vScreen[nRotateGame & 2 ? 0 : 3].tu1 = dScanOffset + (double)nGameWidth;	vScreen[nRotateGame & 2 ? 0 : 3].tv1 = dScanOffset + (double)nGameHeight / RENDER_STRIPS + 0.0000001;
				}
			}
		}

		{
			D3DLVERTEX2* pVertexData;

			if (FAILED(pVB[y]->Lock(0, 4 * sizeof(D3DLVERTEX2), (void**)&pVertexData, 0))) {
#ifdef PRINT_DEBUG_INFO
			   	dprintf(_T("  * Error: Couldn't create vertex buffer.\n"));
#endif
				return 1;
			}
			memcpy(pVertexData, &vScreen, 4 * sizeof(D3DLVERTEX2));
			pVB[y]->Unlock();
		}
	}

	if (bVidScanlines) {
		pEffect->SetTexture("scanTexture", pScanlineTexture[(nImageHeight / nGameImageHeight >= 4) ? 1 : 0]);
	}

	if (pIntermediateTexture) {
		D3DLVERTEX2* pVertexData;
		D3DLVERTEX2 vTemp[4];

		double dTexCoordXl = 0.0;
		double dTexCoordXr = (double)nGameImageWidth / (double)nTextureWidth;
		double dTexCoordYt = 0.0 + 0.0000001;
		double dTexCoordYb = 1.0 + 0.0000001;

		if (DX9_FILTER == 1) {
			dTexCoordYt -= 0.5 / nTextureHeight;
			dTexCoordYb -= 0.5 / nTextureHeight;
		}

		vTemp[0] = D3DLVERTEX2(-1.0,  1.0, 0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYt, 0.5 ,					0.5);
		vTemp[1] = D3DLVERTEX2( 1.0,  1.0, 0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYt, 0.5 + nGameImageWidth, 0.5);
		vTemp[2] = D3DLVERTEX2(-1.0, -1.0, 0.0, 0xFFFFFFFF, 0, dTexCoordXl, dTexCoordYb, 0.5,					0.5 + nGameImageHeight);
		vTemp[3] = D3DLVERTEX2( 1.0, -1.0, 0.0, 0xFFFFFFFF, 0, dTexCoordXr, dTexCoordYb, 0.5 + nGameImageWidth, 0.5 + nGameImageHeight);

		if (FAILED(pIntermediateVB->Lock(0, 4 * sizeof(D3DLVERTEX2), (void**)&pVertexData, 0))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't create vertex buffer.\n"));
#endif
			return 1;
		}

		memcpy(pVertexData, &vTemp, 4 * sizeof(D3DLVERTEX2));
		pIntermediateVB->Unlock();
	}

	return 0;
}

int dx9EffectInit()
{
//	Set up the bicubic filter

//	char szB[MAX_PATH] = "(1.0)";
//	char szC[MAX_PATH] = "(0.0)";
//	D3DXMACRO d3dxm[] = {{ "USE_BC_MACRO", "1", }, { "B", szB, }, { "C", szC, }, { NULL, NULL, }};

	char* pszTechnique;

	ID3DXBuffer* pErrorBuffer = NULL;
	FLOAT pFloatArray[2];

	pEffect = NULL;
	pEffectShader = NULL;

	D3DXCreateBuffer(0x10000, &pErrorBuffer);

#ifdef LOAD_EFFECT_FROM_FILE
	if (FAILED(D3DXCreateEffectFromFile(pD3DDevice, _T("bicubic.fx"), NULL /* d3dxm */, NULL, 0, NULL, &pEffect, &pErrorBuffer))) {
#else
	if (FAILED(D3DXCreateEffectFromResource(pD3DDevice, NULL, MAKEINTRESOURCE(ID_DX9EFFECT), NULL /* d3dxm */, NULL, 0, NULL, &pEffect, &pErrorBuffer))) {
#endif
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't compile effect.\n"));
		dprintf(_T("\n%hs\n\n"), pErrorBuffer->GetBufferPointer());
#endif
		goto HANDLE_ERROR;
	}

	// Check if we need to use PS1.4 (instead of 2.0)
	bUsePS14 = !DX9_USE_PS20;
	if (pEffect->GetTechniqueByName("SinglePassHQBicubic") == NULL) {
		bUsePS14 = true;
	}

	if (dx9EffectSurfaceInit()) {
		goto HANDLE_ERROR;
	}

	switch (DX9_FILTER) {
		case 1: {
			pszTechnique = bVidScanlines ? "ScanBilinear" : "Bilinear";
			break;
		}
		case 2: {
			if (bVidScanlines) {
				char* pszFunction[8] = { "ScanHQBicubic",  "ScanBicubic", "ScanHQBicubic",  "ScanBicubic", "ScanHP20Bicubic", "ScanHP14Bicubic", "ScanHP14Bicubic", "ScanHP14Bicubic" };
				pszTechnique = pszFunction[bUsePS14 ? 7 : DX9_SHADERPRECISION];
			} else {
				char* pszFunction[8] = { "SinglePassHQBicubic", "SinglePassBicubic", "MultiPassHQBicubic", "MultiPassBicubic", "MultiPassHP20Bicubic", "MultiPassHP14Bicubic", "MultiPassHP14Bicubic", "MultiPassHP14Bicubic" };
				pszTechnique = pszFunction[bUsePS14 ? 7 : DX9_SHADERPRECISION];
			}
			break;
		}
		default: {
			pszTechnique = bVidScanlines ? "ScanPoint" : "Point";
			break;
		}
	}

	pEffect->SetTexture("imageTexture", pTexture);
	pEffect->SetTexture("intermediateTexture", pIntermediateTexture);
	pEffect->SetTexture("scanTexture", pScanlineTexture[(nImageHeight / nGameImageHeight >= 4) ? 1 : 0]);

	pFloatArray[0] = nTextureWidth; pFloatArray[1] = nTextureHeight;
	pEffect->SetFloatArray("imageSize", pFloatArray, 2);
	pFloatArray[0] = 1.0 / nTextureWidth; pFloatArray[1] = 1.0 / nTextureHeight;
	pEffect->SetFloatArray("texelSize", pFloatArray, 2);

/*
	{
		FLOAT fScanIntensity[4] = { (float)((nVidScanIntensity >> 16) & 255) / 255.0,
									(float)((nVidScanIntensity >>  8) & 255) / 255.0,
									(float)((nVidScanIntensity >>  0) & 255) / 255.0,
									(float)((nVidScanIntensity >> 24) & 255) / 255.0 };

		pEffect->SetFloatArray("scanIntensity", fScanIntensity, 4);
	}
*/
	hTechnique = pEffect->GetTechniqueByName(pszTechnique);
	if (FAILED(pEffect->SetTechnique(hTechnique))) {
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't set technique.\n"));
#endif
		goto HANDLE_ERROR;
	}

	pEffect->SetTexture("weightTex", pEffectTexture);

	{
		ID3DXBuffer* pEffectShaderBuffer = NULL;
		D3DXCreateBuffer(0x10000, &pEffectShaderBuffer);

#ifdef LOAD_EFFECT_FROM_FILE
		if (FAILED(D3DXCompileShaderFromFile(_T("bicubic.fx"), NULL, NULL, (DX9_SHADERPRECISION < 4 && !bUsePS14) ? "genWeightTex20" : "genWeightTex14", "tx_1_0", 0, &pEffectShaderBuffer, &pErrorBuffer, NULL))) {
#else
		if (FAILED(D3DXCompileShaderFromResource(NULL, MAKEINTRESOURCE(ID_DX9EFFECT), NULL, NULL, (DX9_SHADERPRECISION < 4 && !bUsePS14) ? "genWeightTex20" : "genWeightTex14", "tx_1_0", 0, &pEffectShaderBuffer, &pErrorBuffer, NULL))) {
#endif
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't compile shader.\n"));
			dprintf(_T("\n%hs\n\n"), pErrorBuffer->GetBufferPointer());
#endif
			goto HANDLE_ERROR;
		}

		if (FAILED(D3DXCreateTextureShader((DWORD*)(pEffectShaderBuffer->GetBufferPointer()), &pEffectShader))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't create texture shader.\n"));
#endif
			RELEASE(pEffectShaderBuffer);

			goto HANDLE_ERROR;
		}

		FillEffectTexture();

		RELEASE(pEffectShaderBuffer);
	}

	RELEASE(pErrorBuffer);

	return 0;

HANDLE_ERROR:

	RELEASE(pErrorBuffer);

	return 1;
}

static int dx9Init()
{
	DWORD dwBehaviorFlags;

#ifdef ENABLE_PROFILING
	ProfileInit();
#endif

#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("*** Initialising Direct3D 9 blitter.\n"));
#endif

	if (hScrnWnd == NULL) {
		return 1;
	}

	hVidWnd = hScrnWnd;								// Use Screen window for video

	// Get pointer to Direct3D
	if ((pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL) {
#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't initialise Direct3D.\n"));
#endif
		dx9Exit();
		return 1;
	}

	memset(&d3dpp, 0, sizeof(d3dpp));
	if (nVidFullscreen) {
		VidSDisplayScoreInfo ScoreInfo;
		if (dx9SelectFullscreenMode(&ScoreInfo)) {
			dx9Exit();
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't determine display mode.\n"));
#endif
			return 1;
		}

		d3dpp.BackBufferWidth = ScoreInfo.nBestWidth;
		d3dpp.BackBufferHeight = ScoreInfo.nBestHeight;
		d3dpp.BackBufferFormat = (nVidDepth == 16) ? D3DFMT_R5G6B5 : D3DFMT_X8R8G8B8;
		d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;
		d3dpp.BackBufferCount = bVidTripleBuffer ? 2 : 1;
		d3dpp.hDeviceWindow = hVidWnd;
		d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
		d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
	} else {
		d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
		d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
		d3dpp.BackBufferCount = 1;
		d3dpp.hDeviceWindow = hVidWnd;
		d3dpp.Windowed = TRUE;
		d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	}

	dwBehaviorFlags  = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE;
#ifdef _DEBUG
	dwBehaviorFlags |= D3DCREATE_DISABLE_DRIVER_MANAGEMENT;
#endif

	if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hVidWnd, dwBehaviorFlags, &d3dpp, &pD3DDevice))) {
//	if (FAILED(pD3D->CreateDevice(pD3D->GetAdapterCount() - 1, D3DDEVTYPE_REF, hVidWnd, dwBehaviorFlags, &d3dpp, &pD3DDevice))) {

#ifdef PRINT_DEBUG_INFO
	   	dprintf(_T("  * Error: Couldn't create Direct3D device.\n"));
#endif

		if (nVidFullscreen) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_UI_FULL_PROBLEM));
			if (bVidArcaderes && (d3dpp.BackBufferWidth != 320 && d3dpp.BackBufferHeight != 240)) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_UI_FULL_CUSTRES));
			}
			FBAPopupDisplay(PUF_TYPE_ERROR);
		}

		dx9Exit();
		return 1;
	}

	{
		D3DDISPLAYMODE dm;

		pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dm);
		nVidScrnWidth = dm.Width; nVidScrnHeight = dm.Height;
		nVidScrnDepth = (dm.Format == D3DFMT_R5G6B5) ? 16 : 32;
	}

	nGameWidth = nVidImageWidth; nGameHeight = nVidImageHeight;

	nRotateGame = 0;
	if (bDrvOkay) {
		// Get the game screen size
		BurnDrvGetVisibleSize(&nGameWidth, &nGameHeight);

	    if (BurnDrvGetFlags() & BDF_ORIENTATION_VERTICAL) {
			if (nVidRotationAdjust & 1) {
				int n = nGameWidth;
				nGameWidth = nGameHeight;
				nGameHeight = n;
				nRotateGame |= (nVidRotationAdjust & 2);
			} else {
				nRotateGame |= 1;
			}
		}

		if (BurnDrvGetFlags() & BDF_ORIENTATION_FLIPPED) {
			nRotateGame ^= 2;
		}
	}

//	VidSSetupGamma(BlitFXPrim);

	// Initialize the buffer surfaces
	if (dx9SurfaceInit()) {
		dx9Exit();
		return 1;
	}
	if (dx9EffectInit()) {
		dx9Exit();
		return 1;
	}

	for (int y = 0; y < RENDER_STRIPS; y++) {
		if (FAILED(pD3DDevice->CreateVertexBuffer(4 * sizeof(D3DLVERTEX2), D3DUSAGE_WRITEONLY, D3DFVF_LVERTEX2, D3DPOOL_DEFAULT, &pVB[y], NULL))) {
			dx9Exit();
			return 1;
		}
	}
	if (FAILED(pD3DDevice->CreateVertexBuffer(4 * sizeof(D3DLVERTEX2), D3DUSAGE_WRITEONLY, D3DFVF_LVERTEX2, D3DPOOL_DEFAULT, &pIntermediateVB, NULL))) {
		dx9Exit();
		return 1;
	}

	nImageWidth = 0; nImageHeight = 0;

	dPrevCubicB = dPrevCubicC = -999;

	pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

	// Clear the swapchain's buffers
	if (nVidFullscreen) {
		for (int i = 0; i < 3; i++) {
			pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
			pD3DDevice->Present(NULL, NULL, NULL, NULL);
		}
	} else {
		RECT rect;

		GetClientScreenRect(hVidWnd, &rect);
		rect.top += nMenuHeight; rect.bottom += nMenuHeight;
		pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
		pD3DDevice->Present(&rect, &rect, NULL, NULL);
	}

#ifdef PRINT_DEBUG_INFO
	{
	   	dprintf(_T("  * Initialisation complete: %.2lfMB texture memory free (total).\n"), (double)pD3DDevice->GetAvailableTextureMem() / (1024 * 1024));
		dprintf(_T("    Displaying and rendering in %i-bit mode, emulation running in %i-bit mode.\n"), nVidScrnDepth, nVidImageDepth);
		if (nVidFullscreen) {
			dprintf(_T("    Running in fullscreen mode (%i x %i), "), nVidScrnWidth, nVidScrnHeight);
			dprintf(_T("using a %s buffer.\n"), bVidTripleBuffer ? _T("triple") : _T("double"));
		} else {
			dprintf(_T("    Running in windowed mode, using D3DSWAPEFFECT_COPY to present the image.\n"));
		}
	}
#endif

	return 0;
}

static int dx9Reset()
{
#ifdef PRINT_DEBUG_INFO
   	dprintf(_T("*** Resestting Direct3D device.\n"));
#endif

	dx9ReleaseResources();

	if (FAILED(pD3DDevice->Reset(&d3dpp))) {
		return 1;
	}

	dx9SurfaceInit();
	dx9EffectInit();

	for (int y = 0; y < RENDER_STRIPS; y++) {
		pD3DDevice->CreateVertexBuffer(4 * sizeof(D3DLVERTEX2), D3DUSAGE_WRITEONLY, D3DFVF_LVERTEX2, D3DPOOL_DEFAULT, &pVB[y], NULL);
	}
	pD3DDevice->CreateVertexBuffer(4 * sizeof(D3DLVERTEX2), D3DUSAGE_WRITEONLY, D3DFVF_LVERTEX2, D3DPOOL_DEFAULT, &pIntermediateVB, NULL);

	pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	pD3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

	nImageWidth = 0; nImageHeight = 0;

	dPrevCubicB = dPrevCubicC = -999;

	return 0;
}

static int dx9Scale(RECT* pRect, int nWidth, int nHeight)
{
	return VidSScaleImage(pRect, nWidth, nHeight, bVidScanRotate);
}

// Copy BlitFXsMem to pddsBlitFX
static int dx9MemToSurf()
{
	GetClientScreenRect(hVidWnd, &Dest);

	if (nVidFullscreen == 0) {
		Dest.top += nMenuHeight;
	}

	if (bVidArcaderes && nVidFullscreen) {
		Dest.left = (Dest.right + Dest.left) / 2;
		Dest.left -= nGameWidth / 2;
		Dest.right = Dest.left + nGameWidth;

		Dest.top = (Dest.top + Dest.bottom) / 2;
		Dest.top -= nGameHeight / 2;
		Dest.bottom = Dest.top + nGameHeight;
	} else {
		dx9Scale(&Dest, nGameWidth, nGameHeight);
	}

	{
		int nNewImageWidth  = nRotateGame ? (Dest.bottom - Dest.top) : (Dest.right - Dest.left);
		int nNewImageHeight = nRotateGame ? (Dest.right - Dest.left) : (Dest.bottom - Dest.top);

		if (nImageWidth != nNewImageWidth || nImageHeight != nNewImageHeight) {
			nImageWidth  = nNewImageWidth;
			nImageHeight = nNewImageHeight;
			dx9GeometryInit();
		}
	}

	{
		if (dPrevCubicB != dVidCubicB || dPrevCubicC != dVidCubicC) {
			dPrevCubicB = dVidCubicB;
			dPrevCubicC = dVidCubicC;
			FillEffectTexture();
		}
	}

	{
		// Copy the game image to a surface in video memory

		D3DLOCKED_RECT lr;

		if (FAILED(pSurface->LockRect(&lr, NULL, 0))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't lock surface.\n"));
#endif
			return 1;
		}

		unsigned char* ps = pVidImage + nVidImageLeft * nVidImageBPP;
		unsigned char* pd = (unsigned char*)lr.pBits;
		int p = lr.Pitch;
		int s = nVidImageWidth * nVidImageBPP;

		for (int y = 0; y < nVidImageHeight; y++, pd += p, ps += nVidImagePitch) {
			memcpy(pd, ps, s);
		}

		pSurface->UnlockRect();
	}

	{
		// Copy the game image onto a texture for rendering

		RECT rect = { 0, 0, nVidImageWidth, nVidImageHeight };
		IDirect3DSurface9* pDest;

		if (FAILED(pTexture->GetSurfaceLevel(0, &pDest))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't get texture surface.\n"));
		   	return 1;
#endif
		}

		if (FAILED(pD3DDevice->StretchRect(pSurface, &rect, pDest, &rect, D3DTEXF_NONE))) {
#ifdef PRINT_DEBUG_INFO
		   	dprintf(_T("  * Error: Couldn't copy image.\n"));
#endif
		}

		pDest->Release();
	}

#ifdef ENABLE_PROFILING
	{
		// Force the D3D pipeline to be flushed

		D3DLVERTEX2* pVertexData;

		if (SUCCEEDED(pIntermediateVB->Lock(0, 4 * sizeof(D3DLVERTEX2), (void**)&pVertexData, 0))) {
			pIntermediateVB->Unlock();
		}

		ProfileProfileStart(2);
	}
#endif

	pD3DDevice->SetRenderState(D3DRS_TEXTUREFACTOR, nVidScanIntensity);

	UINT nTotalPasses, nPass = 0;
	pEffect->Begin(&nTotalPasses, D3DXFX_DONOTSAVESTATE);

	if (nTotalPasses > 1) {
		IDirect3DSurface9* pPreviousTarget;
		IDirect3DSurface9* pNewTarget;
		D3DVIEWPORT9 vp;

		pD3DDevice->GetRenderTarget(0, &pPreviousTarget);
		pIntermediateTexture->GetSurfaceLevel(0, &pNewTarget);
		pD3DDevice->SetRenderTarget(0, pNewTarget);

		vp.X = 0;
		vp.Y = 0;
		vp.Width = nImageWidth;
		vp.Height = nTextureHeight;
		vp.MinZ = 0.0f;
		vp.MaxZ = 1.0f;

		pD3DDevice->SetViewport(&vp);

		pD3DDevice->BeginScene();

		pD3DDevice->SetFVF(D3DFVF_LVERTEX2);

		pEffect->BeginPass(nPass);
		pEffect->CommitChanges();

		pD3DDevice->SetStreamSource(0, pIntermediateVB, 0, sizeof(D3DLVERTEX2));
		pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
		pEffect->EndPass();

		pD3DDevice->EndScene();

		pD3DDevice->SetRenderTarget(0, pPreviousTarget);

		RELEASE(pPreviousTarget);
		RELEASE(pNewTarget);

		nPass++;
	}

#if 0
	{
		// blit the intermediate texture to the screen

		RECT srect = { 0, 0, Dest.right - Dest.left, 256 };
		RECT drect = { 0, nMenuHeight, Dest.right - Dest.left, nMenuHeight + 256 };
		IDirect3DSurface9* pDest;
		IDirect3DSurface9* pSrc;

		pD3DDevice->GetRenderTarget(0, &pDest);
		pIntermediateTexture->GetSurfaceLevel(0, &pSrc);

		pD3DDevice->StretchRect(pSrc, &srect, pDest, &drect, D3DTEXF_NONE);

		pSrc->Release();
		pDest->Release();

		pEffect->End();

		return 0;
	}
#endif

#ifdef ENABLE_PROFILING
	{
		// Force the D3D pipeline to be flushed

		D3DLVERTEX2* pVertexData;

		if (SUCCEEDED(pIntermediateVB->Lock(0, 4 * sizeof(D3DLVERTEX2), (void**)&pVertexData, 0))) {
			pIntermediateVB->Unlock();
		}

		ProfileProfileEnd(2);
	}
	ProfileProfileStart(0);
#endif

	{
		D3DVIEWPORT9 vp;

		// Set the size of the image on the PC screen
		if (nVidFullscreen) {
			vp.X = Dest.left;
			vp.Y = Dest.top;
			vp.Width = Dest.right - Dest.left;
			vp.Height = Dest.bottom - Dest.top;
			vp.MinZ = 0.0f;
			vp.MaxZ = 1.0f;
		} else {
			vp.X = 0;
			vp.Y = 0;
			vp.Width = Dest.right - Dest.left;
			vp.Height = Dest.bottom - Dest.top;
			vp.MinZ = 0.0f;
			vp.MaxZ = 1.0f;
		}

		pD3DDevice->SetViewport(&vp);

		pD3DDevice->BeginScene();

		pD3DDevice->SetFVF(D3DFVF_LVERTEX2);

//		pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);

		pEffect->BeginPass(nPass);
		pEffect->CommitChanges();

		for (int y = 0; y < RENDER_STRIPS; y++) {
			pD3DDevice->SetStreamSource(0, pVB[y], 0, sizeof(D3DLVERTEX2));
			pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
		}
		pEffect->EndPass();

		pD3DDevice->EndScene();
	}

	pEffect->End();

#ifdef ENABLE_PROFILING
	{
		// Force the D3D pipeline to be flushed

		D3DLVERTEX2* pVertexData;

		if (SUCCEEDED(pVB[RENDER_STRIPS - 1]->Lock(0, 4 * sizeof(D3DLVERTEX2), (void**)&pVertexData, 0))) {
			pVB[RENDER_STRIPS - 1]->Unlock();
		}

		ProfileProfileEnd(0);
	}
#endif

	return 0;
}

// Run one frame and render the screen
static int dx9Frame(bool bRedraw)								// bRedraw = 0
{
	HRESULT nCoopLevel;

	if (pVidImage == NULL) {
		return 1;
	}

	if ((nCoopLevel = pD3DDevice->TestCooperativeLevel()) != D3D_OK) {		// We've lost control of the screen
		if (nCoopLevel != D3DERR_DEVICENOTRESET) {
			return 1;
		}

		if (dx9Reset()) {
			return 1;
		}
	}

#ifdef ENABLE_PROFILING
//	ProfileProfileStart(0);
#endif
	if (bDrvOkay) {
		if (bRedraw) {								// Redraw current frame
			if (BurnDrvRedraw()) {
				BurnDrvFrame();						// No redraw function provided, advance one frame
			}
		} else {
			BurnDrvFrame();							// Run one frame and draw the screen
		}
	}
#ifdef ENABLE_PROFILING
//	ProfileProfileEnd(0);
	ProfileProfileStart(1);
#endif
	dx9MemToSurf();									// Copy the memory buffer to the directdraw buffer for later blitting
#ifdef ENABLE_PROFILING
	ProfileProfileEnd(1);

	dprintf(_T("    blit %3.2lf (effect p1 %3.2lf - effect p2 %3.2lf)\n"), ProfileProfileReadAverage(1), ProfileProfileReadAverage(2), ProfileProfileReadAverage(0));
#endif

	return 0;
}

// Paint the BlitFX surface onto the primary surface
static int dx9Paint(int bValidate)
{
	RECT rect = { 0, 0, 0, 0 };

	if (pD3DDevice->TestCooperativeLevel()) {		// We've lost control of the screen
		return 1;
	}

	if (!nVidFullscreen) {
		GetClientScreenRect(hVidWnd, &rect);
		rect.top += nMenuHeight;

		dx9Scale(&rect, nGameWidth, nGameHeight);

		if ((rect.right - rect.left) != (Dest.right - Dest.left) || (rect.bottom - rect.top ) != (Dest.bottom - Dest.top)) {
			bValidate |= 2;
		}
	}

	if (bValidate & 2) {
		dx9MemToSurf();								// Copy the memory buffer to the directdraw buffer for later blitting
	}

	// Display OSD text message
//	VidSDisplayOSD(pddsBlitFX[nUseSys], &rect, 0);

	if (nVidFullscreen) {
		pD3DDevice->Present(NULL, NULL, NULL, NULL);
	} else {
		RECT src = { 0, 0, Dest.right - Dest.left, Dest.bottom - Dest.top };

		POINT c = { 0, 0 };
		ClientToScreen(hVidWnd, &c);
		RECT dst = { rect.left - c.x, rect.top - c.y, rect.right - c.x, rect.bottom - c.y };

		if (bVidVSync) {
			D3DRASTER_STATUS rs;
			RECT window;

			GetWindowRect(hVidWnd, &window);

			while (1) {
				pD3DDevice->GetRasterStatus(0, &rs);
				if (rs.InVBlank || rs.ScanLine >= window.bottom) {
					break;
				}
				Sleep(1);
			}
		}

		pD3DDevice->Present(&src, &dst, NULL, NULL);

		// Validate the rectangle we just drew
		if (bValidate & 1) {
			ValidateRect(hVidWnd, &dst);
		}
	}

	return 0;
}

// ----------------------------------------------------------------------------

static int dx9GetSettings(InterfaceInfo* pInfo)
{
	TCHAR szString[MAX_PATH] = _T("");

	if (nVidFullscreen) {
		if (bVidTripleBuffer) {
			IntInfoAddStringModule(pInfo, _T("Using a triple buffer"));
		} else {
			IntInfoAddStringModule(pInfo, _T("Using a double buffer"));
		}
	} else {
		IntInfoAddStringModule(pInfo, _T("Using D3DSWAPEFFECT_COPY to present the image"));
	}
	switch (DX9_FILTER) {
		case 1: {
			IntInfoAddStringModule(pInfo, _T("Applying linear filter"));
			break;
		}
		case 2: {
			_sntprintf(szString, MAX_PATH, _T("Applying cubic filter using VS1.1 & %s"), bUsePS14 ? _T("PS1.4") : _T("PS2.0"));
			IntInfoAddStringModule(pInfo, szString);

			if (bUsePS14) {
				IntInfoAddStringModule(pInfo, _T("Using high-performance implementation"));
				break;
			}

			if (bVidScanlines) {
				switch (DX9_SHADERPRECISION) {
					case 0:
					case 2:
						IntInfoAddStringModule(pInfo, _T("Using reference implementation"));
						break;
					case 1:
					case 3:
						IntInfoAddStringModule(pInfo, _T("Using partial precision implementation"));
						break;
					case 4:
						IntInfoAddStringModule(pInfo, _T("Using high-performance implementation"));
						break;
				}
			} else {
				switch (DX9_SHADERPRECISION) {
					case 0:
						IntInfoAddStringModule(pInfo, _T("Using single-pass reference implementation"));
						break;
					case 1:
						IntInfoAddStringModule(pInfo, _T("Using partial precision single-pass implementation"));
						break;
					case 2:
						IntInfoAddStringModule(pInfo, _T("Using full precision multi-pass implementation"));
						break;
					case 3:
						IntInfoAddStringModule(pInfo, _T("Using partial precision multi-pass implementation"));
						break;
					case 4:
						IntInfoAddStringModule(pInfo, _T("Using high-performance multi-pass implementation"));
						break;
				}
			}
			if (DX9_USE_FPTEXTURE) {
				_sntprintf(szString, MAX_PATH, _T("Using floating-point textures where applicable"));
				IntInfoAddStringModule(pInfo, szString);
			}
			break;
		}
		default: {
			IntInfoAddStringModule(pInfo, _T("Applying point filter"));
		}
	}
	if (bVidScanlines) {
		_sntprintf(szString, MAX_PATH, _T("Applying scanlines at %.1lf%% brightness"), (double)(nVidScanIntensity & 255) / 2.55);
		IntInfoAddStringModule(pInfo, szString);
	}

	return 0;
}

// The Video Output plugin:
struct VidOut VidOutDX9 = { dx9Init, dx9Exit, dx9Frame, dx9Paint, dx9Scale, dx9GetSettings, _T("DirectX9 Experimental video output") };

