
/* Direct3D 9 graphics renderer */

#include <windows.h>
#include "resource.h"

#include "sysconfig.h"
#include "sysdeps.h"

#if defined (D3D) && defined (GFXFILTER)

#define D3DXFX_LARGEADDRESS_HANDLE
#ifdef D3DXFX_LARGEADDRESS_HANDLE
#define EFFECTCOMPILERFLAGS D3DXFX_LARGEADDRESSAWARE
#else
#define EFFECTCOMPILERFLAGS 0
#endif

#define EFFECT_VERSION 3
#define D3DX9DLL _T("d3dx9_43.dll")
#define TWOPASS 1
#define SHADER 1

#include "options.h"
#include "xwin.h"
#include "custom.h"
#include "drawing.h"
#include "render.h"
#include "win32.h"
#include "win32gfx.h"
#include "gfxfilter.h"
#include "statusline.h"
#include "hq2x_d3d.h"
#include "zfile.h"
#include "uae.h"
#include "gui.h"
#include "threaddep\thread.h"

extern int D3DEX, shaderon, d3ddebug;
int forcedframelatency = -1;

#include <d3d9.h>
#include <d3dx9.h>

#include "direct3d.h"

static const TCHAR *D3DHEAD = _T("-");

static bool showoverlay = true;
static int slicecnt;
static int clearcnt;
static bool debugcolors;
static bool noclear;
static bool cannoclear;

int fakemodewaitms;

static int leds[LED_MAX];

static const TCHAR *overlayleds[] = {
	_T("power"),
	_T("df0"),
	_T("df1"),
	_T("df2"),
	_T("df3"),
	_T("hd"),
	_T("cd"),
	_T("md"),
	_T("net"),
	NULL
};
static const int ledtypes[] = {
	LED_POWER,
	LED_DF0,
	LED_DF1,
	LED_DF2,
	LED_DF3,
	LED_HD,
	LED_CD,
	LED_MD,
	LED_NET
}; 

#define MAX_PASSES 2

#define SHADERTYPE_BEFORE 1
#define SHADERTYPE_AFTER 2
#define SHADERTYPE_MIDDLE 3
#define SHADERTYPE_MASK_BEFORE 4
#define SHADERTYPE_MASK_AFTER 5
#define SHADERTYPE_MASK_MIDDLE 6
#define SHADERTYPE_POST 10

struct shaderdata
{
	int type;
	int psPreProcess;
	int worktex_width;
	int worktex_height;
	int targettex_width;
	int targettex_height;
	LPDIRECT3DTEXTURE9 lpWorkTexture1;
	LPDIRECT3DTEXTURE9 lpWorkTexture2;
	LPDIRECT3DTEXTURE9 lpTempTexture;
	LPDIRECT3DVOLUMETEXTURE9 lpHq2xLookupTexture;
	LPD3DXEFFECT pEffect;
	// Technique stuff
	D3DXHANDLE m_PreprocessTechnique1EffectHandle;
	D3DXHANDLE m_PreprocessTechnique2EffectHandle;
	D3DXHANDLE m_CombineTechniqueEffectHandle;
	// Matrix Handles
	D3DXHANDLE m_MatWorldEffectHandle;
	D3DXHANDLE m_MatViewEffectHandle;
	D3DXHANDLE m_MatProjEffectHandle;
	D3DXHANDLE m_MatWorldViewEffectHandle;
	D3DXHANDLE m_MatViewProjEffectHandle;
	D3DXHANDLE m_MatWorldViewProjEffectHandle;
	// Texture Handles
	D3DXHANDLE m_SourceDimsEffectHandle;
	D3DXHANDLE m_InputDimsEffectHandle;
	D3DXHANDLE m_TargetDimsEffectHandle;
	D3DXHANDLE m_TexelSizeEffectHandle;
	D3DXHANDLE m_ScaleEffectHandle;
	D3DXHANDLE m_SourceTextureEffectHandle;
	D3DXHANDLE m_WorkingTexture1EffectHandle;
	D3DXHANDLE m_WorkingTexture2EffectHandle;
	D3DXHANDLE m_Hq2xLookupTextureHandle;
	// Masks
	LPDIRECT3DTEXTURE9 masktexture;
	int masktexture_w, masktexture_h;
	// Stuff
	D3DXHANDLE framecounterHandle;
};

struct d3d9overlay
{
	struct d3d9overlay *next;
	int id;
	int x, y;
	LPDIRECT3DTEXTURE9 tex;
};

#define MAX_SHADERS (2 * MAX_FILTERSHADERS + 2)
#define SHADER_POST 0

struct d3dstruct
{
	int num;
	int psEnabled, psActive;
	struct shaderdata shaders[MAX_SHADERS];
	LPDIRECT3DTEXTURE9 lpPostTempTexture;
	IDirect3DSurface9 *screenshotsurface;
	D3DFORMAT tformat;
	int d3d_enabled, d3d_ex;
	IDirect3D9 *d3d;
	IDirect3D9Ex *d3dex;
	IDirect3DSwapChain9 *d3dswapchain;
	D3DPRESENT_PARAMETERS dpp;
	D3DDISPLAYMODEEX modeex;
	IDirect3DDevice9 *d3ddev;
	IDirect3DDevice9Ex *d3ddevex;
	D3DSURFACE_DESC dsdbb;
	LPDIRECT3DTEXTURE9 texture1, texture2, usetexture, sltexture, ledtexture, blanktexture;
	LPDIRECT3DTEXTURE9 mask2texture, mask2textureleds[9], mask2textureled_power_dim;
	int mask2textureledoffsets[9 * 2];
	IDirect3DQuery9 *query;
	float mask2texture_w, mask2texture_h, mask2texture_ww, mask2texture_wh;
	float mask2texture_wwx, mask2texture_hhx, mask2texture_minusx, mask2texture_minusy;
	float mask2texture_multx, mask2texture_multy, mask2texture_offsetw;
	LPDIRECT3DTEXTURE9 cursorsurfaced3d;
	uae_u8 *cursorsurfaced3dtexbuf;
	bool cursorsurfaced3dtexbufupdated;
	struct d3d9overlay *extoverlays;
	IDirect3DVertexBuffer9 *vertexBuffer;
	ID3DXSprite *sprite;
	HWND d3dhwnd;
	int devicelost;
	int locked, fulllocked;
	int cursor_offset_x, cursor_offset_y, cursor_offset2_x, cursor_offset2_y;
	float maskmult_x, maskmult_y;
	RECT mask2rect;
	bool wasstilldrawing_broken;
	bool renderdisabled;
	HANDLE filenotificationhandle;
	int frames_since_init;

	volatile bool fakemode;
	uae_u8 *fakebitmap;
	int fakelock;
	uae_thread_id fakemodetid;

	D3DXMATRIXA16 m_matProj, m_matProj2, m_matProj_out;
	D3DXMATRIXA16 m_matWorld, m_matWorld2, m_matWorld_out;
	D3DXMATRIXA16 m_matView, m_matView2, m_matView_out;
	D3DXMATRIXA16 m_matPreProj;
	D3DXMATRIXA16 m_matPreView;
	D3DXMATRIXA16 m_matPreWorld;
	D3DXMATRIXA16 postproj;
	D3DXVECTOR4 maskmult, maskshift;
	D3DXVECTOR4 fakesize;

	int ledwidth, ledheight;
	int max_texture_w, max_texture_h;
	int tin_w, tin_h, tout_w, tout_h, window_h, window_w;
	int tino_w, tino_h;
	int t_depth, dmult, dmultxh, dmultxv, dmode;
	int required_sl_texture_w, required_sl_texture_h;
	int vsync2, guimode, maxscanline, variablerefresh;
	int resetcount;
	float cursor_x, cursor_y;
	float cursor_mx, cursor_my;
	float xmult, ymult;
	bool cursor_v, cursor_scale;
	int statusbar_vx, statusbar_hx;
	RECT sr2, dr2, zr2;

	struct gfx_filterdata *filterd3d;
	int filterd3didx;
	int scanline_osl1, scanline_osl2, scanline_osl3, scanline_osl4;

	D3DXHANDLE postSourceTextureHandle;
	D3DXHANDLE postMaskTextureHandle;
	D3DXHANDLE postTechnique, postTechniquePlain, postTechniqueAlpha;
	D3DXHANDLE postMatrixSource;
	D3DXHANDLE postMaskMult, postMaskShift;
	D3DXHANDLE postFilterMode;
	D3DXHANDLE postTexelSize;
	D3DXHANDLE postFramecounterHandle;

	float m_scale;
	LPCSTR m_strName;
};

static struct d3dstruct d3ddata[MAX_AMIGAMONITORS];

#define NUMVERTICES 8
#define D3DFVF_TLVERTEX D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1
struct TLVERTEX {
	D3DXVECTOR3 position;       // vertex position
	D3DCOLOR    diffuse;
	D3DXVECTOR2 texcoord;       // texture coords
};

static const TCHAR *D3D_ErrorText (HRESULT error)
{
	return _T("");
}
static TCHAR *D3D_ErrorString (HRESULT dival)
{
	static TCHAR dierr[200];
	_stprintf (dierr, _T("%08X S=%d F=%04X C=%04X (%d) (%s)"),
		dival, (dival & 0x80000000) ? 1 : 0,
		HRESULT_FACILITY(dival),
		HRESULT_CODE(dival),
		HRESULT_CODE(dival),
		D3D_ErrorText (dival));
	return dierr;
}

static D3DXMATRIX* MatrixOrthoOffCenterLH(D3DXMATRIXA16 *pOut, float l, float r, float b, float t, float zn, float zf)
{
	pOut->_11=2.0f/r; pOut->_12=0.0f;   pOut->_13=0.0f;  pOut->_14=0.0f;
	pOut->_21=0.0f;   pOut->_22=2.0f/t; pOut->_23=0.0f;  pOut->_24=0.0f;
	pOut->_31=0.0f;   pOut->_32=0.0f;   pOut->_33=1.0f;  pOut->_34=0.0f;
	pOut->_41=-1.0f;  pOut->_42=-1.0f;  pOut->_43=0.0f;  pOut->_44=1.0f;
	return pOut;
}

static D3DXMATRIX* MatrixScaling(D3DXMATRIXA16 *pOut, float sx, float sy, float sz)
{
	pOut->_11=sx;     pOut->_12=0.0f;   pOut->_13=0.0f;  pOut->_14=0.0f;
	pOut->_21=0.0f;   pOut->_22=sy;     pOut->_23=0.0f;  pOut->_24=0.0f;
	pOut->_31=0.0f;   pOut->_32=0.0f;   pOut->_33=sz;    pOut->_34=0.0f;
	pOut->_41=0.0f;   pOut->_42=0.0f;   pOut->_43=0.0f;  pOut->_44=1.0f;
	return pOut;
}

static D3DXMATRIX* MatrixTranslation(D3DXMATRIXA16 *pOut, float tx, float ty, float tz)
{
	pOut->_11=1.0f;   pOut->_12=0.0f;   pOut->_13=0.0f;  pOut->_14=0.0f;
	pOut->_21=0.0f;   pOut->_22=1.0f;   pOut->_23=0.0f;  pOut->_24=0.0f;
	pOut->_31=0.0f;   pOut->_32=0.0f;   pOut->_33=1.0f;  pOut->_34=0.0f;
	pOut->_41=tx;     pOut->_42=ty;     pOut->_43=tz;    pOut->_44=1.0f;
	return pOut;
}

static TCHAR *D3DX_ErrorString (HRESULT hr, LPD3DXBUFFER Errors)
{
	static TCHAR *buffer;
	static int buffersize;
	TCHAR *s = NULL;
	int size = 0;

	if (Errors)
		s = au ((char*)Errors->GetBufferPointer ());
	size = (s == NULL ? 0 : uaetcslen (s)) + 1000;
	if (size + 1000 > buffersize) {
		xfree (buffer);
		buffer = xmalloc (TCHAR, size);
		buffersize = size;
	}
	buffer[0] = 0;
	if (hr != S_OK)
		_tcscpy (buffer, D3D_ErrorString (hr));
	if (s) {
		if (buffer[0])
			_tcscat (buffer, _T(" "));
		_tcscat (buffer, s);
	}
	xfree (s);
	return buffer;
}

static int isd3d (struct d3dstruct *d3d)
{
	if (d3d->fakemode || d3d->devicelost || !d3d->d3ddev || !d3d->d3d_enabled || d3d->renderdisabled)
		return 0;
	return 1;
}
static void waitfakemode (struct d3dstruct *d3d)
{
	while (d3d->fakemode) {
		sleep_millis (10);
	}
}


enum psEffect_Pass { psEffect_None, psEffect_PreProcess1, psEffect_PreProcess2, psEffect_Combine };

static int postEffect_ParseParameters (struct d3dstruct *d3d, LPD3DXEFFECTCOMPILER EffectCompiler, LPD3DXEFFECT effect, struct shaderdata *s)
{
	d3d->postSourceTextureHandle = effect->GetParameterByName (NULL, "SourceTexture");
	d3d->postMaskTextureHandle = effect->GetParameterByName (NULL, "OverlayTexture");
	d3d->postTechnique = effect->GetTechniqueByName ("PostTechnique");
	d3d->postTechniquePlain = effect->GetTechniqueByName ("PostTechniquePlain");
	d3d->postTechniqueAlpha = effect->GetTechniqueByName ("PostTechniqueAlpha");
	d3d->postMatrixSource = effect->GetParameterByName (NULL, "mtx");
	d3d->postMaskMult = effect->GetParameterByName (NULL, "maskmult");
	d3d->postMaskShift = effect->GetParameterByName (NULL, "maskshift");
	d3d->postFilterMode = effect->GetParameterByName (NULL, "filtermode");
	d3d->postTexelSize = effect->GetParameterByName (NULL, "texelsize");
	d3d->postFramecounterHandle = effect->GetParameterByName (NULL, "framecounter");

	if (!d3d->postMaskShift || !d3d->postMaskMult || !d3d->postFilterMode || !d3d->postMatrixSource || !d3d->postTexelSize) {
		gui_message (_T("Mismatched _winuae.fx! Exiting.."));
		abort ();
	}
	return true;
}

static void postEffect_freeParameters(struct d3dstruct *d3d)
{
	d3d->postSourceTextureHandle = NULL;
	d3d->postMaskTextureHandle = NULL;
	d3d->postTechnique = NULL;
	d3d->postTechniquePlain = NULL;
	d3d->postTechniqueAlpha = NULL;
	d3d->postMatrixSource = NULL;
	d3d->postMaskMult = NULL;
	d3d->postMaskShift = NULL;
	d3d->postFilterMode = NULL;
	d3d->postTexelSize = NULL;
	d3d->postFramecounterHandle = NULL;
}

static int psEffect_ParseParameters (struct d3dstruct *d3d, LPD3DXEFFECTCOMPILER EffectCompiler, LPD3DXEFFECT effect, D3DXEFFECT_DESC EffectDesc, struct shaderdata *s)
{
	HRESULT hr = S_OK;
	// Look at parameters for semantics and annotations that we know how to interpret
	D3DXPARAMETER_DESC ParamDesc;
	D3DXPARAMETER_DESC AnnotDesc;
	D3DXHANDLE hParam;
	D3DXHANDLE hAnnot;
	LPDIRECT3DBASETEXTURE9 pTex = NULL;
	UINT iParam, iAnnot;


	if(effect == NULL)
		return 0;

	for(iParam = 0; iParam < EffectDesc.Parameters; iParam++) {
		LPCSTR pstrName = NULL;
		LPCSTR pstrFunction = NULL;
		D3DXHANDLE pstrFunctionHandle = NULL;
		LPCSTR pstrTarget = NULL;
		LPCSTR pstrTextureType = NULL;
		INT Width = D3DX_DEFAULT;
		INT Height= D3DX_DEFAULT;
		INT Depth = D3DX_DEFAULT;

		hParam = effect->GetParameter (NULL, iParam);
		hr = effect->GetParameterDesc (hParam, &ParamDesc);
		if (FAILED (hr)) {
			write_log (_T("GetParameterDescParm(%d) failed: %s\n"), D3DHEAD, iParam, D3DX_ErrorString (hr, NULL));
			return 0;
		}
		s->framecounterHandle = effect->GetParameterByName (NULL, "framecounter");
		hr = S_OK;
		if(ParamDesc.Semantic != NULL) {
			if(ParamDesc.Class == D3DXPC_MATRIX_ROWS || ParamDesc.Class == D3DXPC_MATRIX_COLUMNS) {
				if(strcmpi(ParamDesc.Semantic, "world") == 0)
					s->m_MatWorldEffectHandle = hParam;
				else if(strcmpi(ParamDesc.Semantic, "view") == 0)
					s->m_MatViewEffectHandle = hParam;
				else if(strcmpi(ParamDesc.Semantic, "projection") == 0)
					s->m_MatProjEffectHandle = hParam;
				else if(strcmpi(ParamDesc.Semantic, "worldview") == 0)
					s->m_MatWorldViewEffectHandle = hParam;
				else if(strcmpi(ParamDesc.Semantic, "viewprojection") == 0)
					s->m_MatViewProjEffectHandle = hParam;
				else if(strcmpi(ParamDesc.Semantic, "worldviewprojection") == 0)
					s->m_MatWorldViewProjEffectHandle = hParam;
			} else if(ParamDesc.Class == D3DXPC_VECTOR && ParamDesc.Type == D3DXPT_FLOAT) {
				if (strcmpi(ParamDesc.Semantic, "sourcedims") == 0)
					s->m_SourceDimsEffectHandle = hParam;
				if (strcmpi(ParamDesc.Semantic, "inputdims") == 0)
					s->m_InputDimsEffectHandle = hParam;
				if (strcmpi(ParamDesc.Semantic, "targetdims") == 0)
					s->m_TargetDimsEffectHandle = hParam;
				else if (strcmpi(ParamDesc.Semantic, "texelsize") == 0)
					s->m_TexelSizeEffectHandle = hParam;
				else if (strcmpi(ParamDesc.Semantic, "sourcescale") == 0)
					s->m_ScaleEffectHandle = hParam;
			} else if(ParamDesc.Class == D3DXPC_SCALAR && ParamDesc.Type == D3DXPT_FLOAT) {
				if(strcmpi(ParamDesc.Semantic, "SCALING") == 0)
					hr = effect->GetFloat(hParam, &d3d->m_scale);
			} else if(ParamDesc.Class == D3DXPC_OBJECT && ParamDesc.Type == D3DXPT_TEXTURE) {
				if(strcmpi(ParamDesc.Semantic, "SOURCETEXTURE") == 0)
					s->m_SourceTextureEffectHandle = hParam;
				if(strcmpi(ParamDesc.Semantic, "WORKINGTEXTURE") == 0)
					s->m_WorkingTexture1EffectHandle = hParam;
				if(strcmpi(ParamDesc.Semantic, "WORKINGTEXTURE1") == 0)
					s->m_WorkingTexture2EffectHandle = hParam;
				if(strcmpi(ParamDesc.Semantic, "HQ2XLOOKUPTEXTURE") == 0)
					s->m_Hq2xLookupTextureHandle = hParam;
			} else if(ParamDesc.Class == D3DXPC_OBJECT && ParamDesc.Type == D3DXPT_STRING) {
				LPCSTR pstrTechnique = NULL;
				if(strcmpi(ParamDesc.Semantic, "COMBINETECHNIQUE") == 0) {
					hr = effect->GetString(hParam, &pstrTechnique);
					s->m_CombineTechniqueEffectHandle = effect->GetTechniqueByName(pstrTechnique);
				} else if(strcmpi(ParamDesc.Semantic, "PREPROCESSTECHNIQUE") == 0) {
					hr = effect->GetString(hParam, &pstrTechnique);
					s->m_PreprocessTechnique1EffectHandle = effect->GetTechniqueByName(pstrTechnique);
				} else if(strcmpi(ParamDesc.Semantic, "PREPROCESSTECHNIQUE1") == 0) {
					hr = effect->GetString(hParam, &pstrTechnique);
					s->m_PreprocessTechnique2EffectHandle = effect->GetTechniqueByName(pstrTechnique);
				} else if(strcmpi(ParamDesc.Semantic, "NAME") == 0) {
					hr = effect->GetString(hParam, &d3d->m_strName);
				}
			}
			if (FAILED (hr)) {
				write_log (_T("ParamDesc.Semantic failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, NULL));
				return 0;
			}
		}

		for(iAnnot = 0; iAnnot < ParamDesc.Annotations; iAnnot++) {
			hAnnot = effect->GetAnnotation (hParam, iAnnot);
			hr = effect->GetParameterDesc(hAnnot, &AnnotDesc);
			if (FAILED (hr)) {
				write_log (_T("GetParameterDescAnnot(%d) failed: %s\n"), D3DHEAD, iAnnot, D3DX_ErrorString (hr, NULL));
				return 0;
			}
			hr = S_OK;
			if(strcmpi(AnnotDesc.Name, "name") == 0) {
				hr = effect->GetString(hAnnot, &pstrName);
			} else if(strcmpi(AnnotDesc.Name, "function") == 0) {
				hr = effect->GetString(hAnnot, &pstrFunction);
				pstrFunctionHandle = effect->GetFunctionByName(pstrFunction);
			} else if(strcmpi(AnnotDesc.Name, "target") == 0) {
				hr = effect->GetString(hAnnot, &pstrTarget);
			} else if(strcmpi(AnnotDesc.Name, "width") == 0) {
				hr = effect->GetInt(hAnnot, &Width);
			} else if(strcmpi(AnnotDesc.Name, "height") == 0) {
				hr = effect->GetInt(hAnnot, &Height);
			} else if(strcmpi(AnnotDesc.Name, "depth") == 0) {
				hr = effect->GetInt(hAnnot, &Depth);
			} else if(strcmpi(AnnotDesc.Name, "type") == 0) {
				hr = effect->GetString(hAnnot, &pstrTextureType);
			}
			if (FAILED (hr)) {
				write_log (_T("GetString/GetInt(%d) failed: %s\n"), D3DHEAD, iAnnot, D3DX_ErrorString (hr, NULL));
				return 0;
			}
		}

		if(pstrFunctionHandle != NULL) {
			LPD3DXBUFFER pTextureShader = NULL;
			LPD3DXBUFFER lpErrors = 0;

			if(pstrTarget == NULL || strcmp(pstrTarget,"tx_1_1"))
				pstrTarget = "tx_1_0";

			if(SUCCEEDED(hr = EffectCompiler->CompileShader(
				pstrFunctionHandle, pstrTarget, D3DXSHADER_SKIPVALIDATION|D3DXSHADER_DEBUG, &pTextureShader, &lpErrors, NULL))) {
					LPD3DXTEXTURESHADER ppTextureShader;
					if (lpErrors)
						lpErrors->Release ();

					if(Width == D3DX_DEFAULT)
						Width = 64;
					if(Height == D3DX_DEFAULT)
						Height = 64;
					if(Depth == D3DX_DEFAULT)
						Depth = 64;

					D3DXCreateTextureShader((DWORD *)pTextureShader->GetBufferPointer(), &ppTextureShader);

					if(pstrTextureType != NULL) {
						if(strcmpi(pstrTextureType, "volume") == 0) {
							LPDIRECT3DVOLUMETEXTURE9 pVolumeTex = NULL;
							if(SUCCEEDED(hr = D3DXCreateVolumeTexture(d3d->d3ddev,
								Width, Height, Depth, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pVolumeTex))) {
									if(SUCCEEDED(hr = D3DXFillVolumeTextureTX(pVolumeTex, ppTextureShader))) {
										pTex = pVolumeTex;
									}
							}
						} else if(strcmpi(pstrTextureType, "cube") == 0) {
							LPDIRECT3DCUBETEXTURE9 pCubeTex = NULL;
							if(SUCCEEDED(hr = D3DXCreateCubeTexture(d3d->d3ddev,
								Width, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pCubeTex))) {
									if(SUCCEEDED(hr = D3DXFillCubeTextureTX(pCubeTex, ppTextureShader))) {
										pTex = pCubeTex;
									}
							}
						}
					} else {
						LPDIRECT3DTEXTURE9 p2DTex = NULL;
						if(SUCCEEDED(hr = D3DXCreateTexture(d3d->d3ddev, Width, Height,
							D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &p2DTex))) {
								if(SUCCEEDED(hr = D3DXFillTextureTX(p2DTex, ppTextureShader))) {
									pTex = p2DTex;
								}
						}
					}
					effect->SetTexture(effect->GetParameter(NULL, iParam), pTex);
					if (pTex)
						pTex->Release ();
					if (pTextureShader)
						pTextureShader->Release ();
					if (ppTextureShader)
						ppTextureShader->Release ();
			} else {
				write_log (_T("%s: Could not compile texture shader: %s\n"), D3DHEAD, D3DX_ErrorString (hr, lpErrors));
				if (lpErrors)
					lpErrors->Release ();
				return 0;
			}
		}
	}

	if (!s->m_CombineTechniqueEffectHandle && EffectDesc.Techniques > 0) {
		s->m_CombineTechniqueEffectHandle = effect->GetTechnique(0);
	}

	return 1;
}

static int psEffect_hasPreProcess (struct shaderdata *s) { return s->m_PreprocessTechnique1EffectHandle != 0; }
static int psEffect_hasPreProcess2 (struct shaderdata *s) { return s->m_PreprocessTechnique2EffectHandle != 0; }

int xD3D_goodenough (void)
{
	static int d3d_good;
	LPDIRECT3D9 d3dx;
	D3DCAPS9 d3dCaps;

	if (d3d_good > 0)
		return d3d_good;
	if (d3d_good < 0)
		return 0;
	d3d_good = -1;
	d3dx = Direct3DCreate9 (D3D_SDK_VERSION);
	if (d3dx != NULL) {
		if (SUCCEEDED (d3dx->GetDeviceCaps (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dCaps))) {
			if (((d3dCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) == D3DPTEXTURECAPS_NONPOW2CONDITIONAL) || !(d3dCaps.TextureCaps & D3DPTEXTURECAPS_POW2)) {
				if (!(d3dCaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY) && (d3dCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES)) {
					d3d_good = 1;
					if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(1, 0)) {
						d3d_good = 2;
						if (shaderon < 0)
							shaderon = 1;
					}
				}
			}
		}
		d3dx->Release ();
	}
#if SHADER == 0
	shaderon = 0;
#endif
	if (shaderon < 0)
		shaderon = 0;
	return d3d_good > 0 ? d3d_good : 0;
}

int xD3D_canshaders (void)
{
	static int d3d_yesno = 0;
	HMODULE h;
	LPDIRECT3D9 d3dx;
	D3DCAPS9 d3dCaps;

	if (d3d_yesno < 0)
		return 0;
	if (d3d_yesno > 0)
		return 1;
	d3d_yesno = -1;
	h = LoadLibrary (D3DX9DLL);
	if (h != NULL) {
		FreeLibrary (h);
		d3dx = Direct3DCreate9 (D3D_SDK_VERSION);
		if (d3dx != NULL) {
			if (SUCCEEDED (d3dx->GetDeviceCaps (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dCaps))) {
				if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(2,0)) {
					write_log (_T("Direct3D: Pixel shader 2.0+ support detected, shader filters enabled.\n"));
					d3d_yesno = 1;
				}
			}
			d3dx->Release ();
		}
	}
	return d3d_yesno > 0 ? 1 : 0;
}

static const char *fx10 = {

"// 3 (version)\n"
"//\n"
"// WinUAE Direct3D post processing shader\n"
"//\n"
"// by Toni Wilen 2012\n"
"\n"
"uniform extern float4x4 mtx;\n"
"uniform extern float2 maskmult;\n"
"uniform extern float2 maskshift;\n"
"uniform extern int filtermode;\n"
"uniform extern float2 texelsize;\n"
"\n"
"// final possibly filtered Amiga output\n"
"texture SourceTexture : SOURCETEXTURE;\n"
"\n"
"sampler	SourceSampler = sampler_state {\n"
"	Texture	  = (SourceTexture);\n"
"	MinFilter = POINT;\n"
"	MagFilter = POINT;\n"
"	MipFilter = NONE;\n"
"	AddressU  = Clamp;\n"
"	AddressV  = Clamp;\n"
"};\n"
"\n"
"\n"
"texture OverlayTexture : OVERLAYTEXTURE;\n"
"\n"
"sampler	OverlaySampler = sampler_state {\n"
"	Texture	  = (OverlayTexture);\n"
"	MinFilter = POINT;\n"
"	MagFilter = POINT;\n"
"	MipFilter = NONE;\n"
"	AddressU  = Wrap;\n"
"	AddressV  = Wrap;\n"
"};\n"
"\n"
"struct VS_OUTPUT_POST\n"
"{\n"
"	float4 Position		: POSITION;\n"
"	float2 CentreUV		: TEXCOORD0;\n"
"	float2 Selector		: TEXCOORD1;\n"
"};\n"
"\n"
"VS_OUTPUT_POST VS_Post(float3 pos : POSITION, float2 TexCoord : TEXCOORD0)\n"
"{\n"
"	VS_OUTPUT_POST Out = (VS_OUTPUT_POST)0;\n"
"\n"
"	Out.Position = mul(float4(pos, 1.0f), mtx);\n"
"	Out.CentreUV = TexCoord;\n"
"	Out.Selector = TexCoord * maskmult + maskshift;\n"
"	return Out;\n"
"}\n"
"\n"
"float4 PS_Post(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
"	return s * o;\n"
"}\n"
"\n"
"float4 PS_PostAlpha(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
"	return s * (1 - o.a) + (o * o.a);\n"
"}\n"
"\n"
"float4 PS_PostPlain(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	return s;\n"
"}\n"
"\n"
"// source and overlay texture\n"
"technique PostTechnique\n"
"{\n"
"    pass P0\n"
"    {\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_1_0 PS_Post();\n"
"    }  \n"
"}\n"
"\n"
"// source and scanline texture with alpha\n"
"technique PostTechniqueAlpha\n"
"{\n"
"	pass P0\n"
"	{\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_1_0 PS_PostAlpha();\n"
"    } \n"
"}\n"
"\n"
"// only source texture\n"
"technique PostTechniquePlain\n"
"{\n"
"	pass P0\n"
"	{\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_1_0 PS_PostPlain();\n"
"    }\n"
"}\n"
};

static const char *fx20 = {

"// 3 (version)\n"
"//\n"
"// WinUAE Direct3D post processing shader\n"
"//\n"
"// by Toni Wilen 2012\n"
"\n"
"uniform extern float4x4 mtx;\n"
"uniform extern float2 maskmult;\n"
"uniform extern float2 maskshift;\n"
"uniform extern int filtermode;\n"
"uniform extern float2 texelsize;\n"
"\n"
"// final possibly filtered Amiga output\n"
"texture SourceTexture : SOURCETEXTURE;\n"
"\n"
"sampler	SourceSampler = sampler_state {\n"
"	Texture	  = (SourceTexture);\n"
"	MinFilter = filtermode;\n"
"	MagFilter = filtermode;\n"
"	MipFilter = NONE;\n"
"	AddressU  = Clamp;\n"
"	AddressV  = Clamp;\n"
"};\n"
"\n"
"\n"
"texture OverlayTexture : OVERLAYTEXTURE;\n"
"\n"
"sampler	OverlaySampler = sampler_state {\n"
"	Texture	  = (OverlayTexture);\n"
"	MinFilter = POINT;\n"
"	MagFilter = POINT;\n"
"	MipFilter = NONE;\n"
"	AddressU  = Wrap;\n"
"	AddressV  = Wrap;\n"
"};\n"
"\n"
"struct VS_OUTPUT_POST\n"
"{\n"
"	float4 Position		: POSITION;\n"
"	float2 CentreUV		: TEXCOORD0;\n"
"	float2 Selector		: TEXCOORD1;\n"
"};\n"
"\n"
"VS_OUTPUT_POST VS_Post(float3 pos : POSITION, float2 TexCoord : TEXCOORD0)\n"
"{\n"
"	VS_OUTPUT_POST Out = (VS_OUTPUT_POST)0;\n"
"\n"
"	Out.Position = mul(float4(pos, 1.0f), mtx);\n"
"	Out.CentreUV = TexCoord;\n"
"	Out.Selector = TexCoord * maskmult + maskshift;\n"
"	return Out;\n"
"}\n"
"\n"
"float4 PS_Post(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
"	return s * o;\n"
"}\n"
"\n"
"float4 PS_PostAlpha(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	float4 o = tex2D(OverlaySampler, inp.Selector);\n"
"	return s * (1 - o.a) + (o * o.a);\n"
"}\n"
"\n"
"float4 PS_PostPlain(in VS_OUTPUT_POST inp) : COLOR\n"
"{\n"
"	float4 s = tex2D(SourceSampler, inp.CentreUV);\n"
"	return s;\n"
"}\n"
"\n"
"// source and overlay texture\n"
"technique PostTechnique\n"
"{\n"
"    pass P0\n"
"    {\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_2_0 PS_Post();\n"
"    }  \n"
"}\n"
"\n"
"// source and scanline texture with alpha\n"
"technique PostTechniqueAlpha\n"
"{\n"
"	pass P0\n"
"	{\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_2_0 PS_PostAlpha();\n"
"    } \n"
"}\n"
"\n"
"// only source texture\n"
"technique PostTechniquePlain\n"
"{\n"
"	pass P0\n"
"	{\n"
"		VertexShader = compile vs_1_0 VS_Post();\n"
"		PixelShader  = compile ps_2_0 PS_PostPlain();\n"
"    }\n"
"}\n"
};	

static bool psEffect_LoadEffect (struct d3dstruct *d3d, const TCHAR *shaderfile, int full, struct shaderdata *s, int num)
{
	int ret = 0;
	LPD3DXEFFECTCOMPILER EffectCompiler = NULL;
	LPD3DXBUFFER Errors = NULL;
	LPD3DXBUFFER BufferEffect = NULL;
	HRESULT hr;
	TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH], tmp3[MAX_DPATH];
	LPD3DXEFFECT effect = NULL;
	static int first;
	DWORD compileflags = d3d->psEnabled ? 0 : D3DXSHADER_USE_LEGACY_D3DX9_31_DLL;
	int canusefile = 0, existsfile = 0;
	bool plugin_path;
	D3DXEFFECT_DESC EffectDesc;
	void *bp;
	int bplen;


	compileflags |= EFFECTCOMPILERFLAGS;
	plugin_path = get_plugin_path (tmp, sizeof tmp / sizeof (TCHAR), _T("filtershaders\\direct3d"));
	_tcscpy (tmp3, tmp);
	_tcscat (tmp, shaderfile);
	if (!full) {
		struct zfile *z = zfile_fopen (tmp, _T("r"));
		if (z) {
			existsfile = 1;
			zfile_fgets (tmp2, sizeof tmp2 / sizeof (TCHAR), z);
			zfile_fclose (z);
			int ver = _tstol (tmp2 + 2);
			if (ver == EFFECT_VERSION) {
				canusefile = 1;
			} else {
				write_log (_T("'%s' mismatched version (%d != %d)\n"), tmp, ver, EFFECT_VERSION);
			}
		}
		hr = E_FAIL;
		if (canusefile) {
			write_log (_T("%s: Attempting to load '%s'\n"), D3DHEAD, tmp);
			hr = D3DXCreateEffectCompilerFromFile (tmp, NULL, NULL, compileflags, &EffectCompiler, &Errors);
			if (FAILED (hr))
				write_log (_T("%s: D3DXCreateEffectCompilerFromFile failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
		}
		if (FAILED (hr)) {
			const char *str = d3d->psEnabled ? fx20 : fx10;
			int len = uaestrlen(str);
			if (!existsfile && plugin_path) {
				struct zfile *z = zfile_fopen (tmp, _T("w"));
				if (z) {
					zfile_fwrite ((void*)str, len, 1, z);
					zfile_fclose (z);
				}
			}
			hr = D3DXCreateEffectCompiler (str, len, NULL, NULL, compileflags, &EffectCompiler, &Errors);
			if (FAILED (hr)) {
				write_log (_T("%s: D3DXCreateEffectCompiler failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
				goto end;
			}
		}
	} else {
		write_log (_T("%s: Attempting to load '%s'\n"), D3DHEAD, tmp);
		hr = D3DXCreateEffectCompilerFromFile (tmp, NULL, NULL, compileflags, &EffectCompiler, &Errors);
		if (FAILED (hr)) {
			write_log (_T("%s: D3DXCreateEffectCompilerFromFile failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
			goto end;
		}
	}

	if (Errors) {
		write_log (_T("%s: '%s' warning: %s\n"), D3DHEAD, shaderfile, D3DX_ErrorString (hr, Errors));
		Errors->Release();
		Errors = NULL;
	}

	hr = EffectCompiler->CompileEffect (0, &BufferEffect, &Errors);
	if (FAILED (hr)) {
		write_log (_T("%s: CompileEffect failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
		goto end;
	}
	bp = BufferEffect->GetBufferPointer ();
	bplen = BufferEffect->GetBufferSize ();
	hr = D3DXCreateEffect (d3d->d3ddev,
		bp, bplen,
		NULL, NULL,
		EFFECTCOMPILERFLAGS,
		NULL, &effect, &Errors);
	if (FAILED (hr)) {
		write_log (_T("%s: D3DXCreateEffect failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
		goto end;
	}
	hr = effect->GetDesc (&EffectDesc);
	if (FAILED (hr)) {
		write_log (_T("%s: effect->GetDesc() failed: %s\n"), D3DHEAD, D3DX_ErrorString (hr, Errors));
		goto end;
	}
	if (full) {
		if (!psEffect_ParseParameters (d3d, EffectCompiler, effect, EffectDesc, s))
			goto end;
	} else {
		if (!postEffect_ParseParameters (d3d, EffectCompiler, effect, s))
			goto end;
	}
	ret = 1;
	d3d->frames_since_init = 0;
	if (plugin_path && d3d->filenotificationhandle == NULL)
		d3d->filenotificationhandle = FindFirstChangeNotification (tmp3, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);

end:
	if (Errors)
		Errors->Release ();
	if (BufferEffect)
		BufferEffect->Release ();
	if (EffectCompiler)
		EffectCompiler->Release ();

	if (full) {
		s->psPreProcess = FALSE;
		if (ret) {
			d3d->psActive = TRUE;
			if (psEffect_hasPreProcess (s))
				s->psPreProcess = TRUE;
		}
	}

	if (ret)
		write_log (_T("%s: pixelshader filter '%s':%d enabled\n"), D3DHEAD, tmp, num);
	else
		write_log (_T("%s: pixelshader filter '%s':%d failed to initialize\n"), D3DHEAD, tmp, num);
	s->pEffect = effect;
	return effect != NULL;
}

static int psEffect_SetMatrices (D3DXMATRIXA16 *matProj, D3DXMATRIXA16 *matView, D3DXMATRIXA16 *matWorld, struct shaderdata *s)
{
	HRESULT hr;

	if (s->m_MatWorldEffectHandle) {
		hr = s->pEffect->SetMatrix (s->m_MatWorldEffectHandle, matWorld);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matWorld %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_MatViewEffectHandle) {
		hr = s->pEffect->SetMatrix (s->m_MatViewEffectHandle, matView);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matView %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_MatProjEffectHandle) {
		hr = s->pEffect->SetMatrix (s->m_MatProjEffectHandle, matProj);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matProj %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_MatWorldViewEffectHandle) {
		D3DXMATRIXA16 matWorldView;
		D3DXMatrixMultiply (&matWorldView, matWorld, matView);
		hr = s->pEffect->SetMatrix (s->m_MatWorldViewEffectHandle, &matWorldView);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matWorldView %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_MatViewProjEffectHandle) {
		D3DXMATRIXA16 matViewProj;
		D3DXMatrixMultiply (&matViewProj, matView, matProj);
		hr = s->pEffect->SetMatrix (s->m_MatViewProjEffectHandle, &matViewProj);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matViewProj %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_MatWorldViewProjEffectHandle) {
		D3DXMATRIXA16 tmp, matWorldViewProj;
		D3DXMatrixMultiply (&tmp, matWorld, matView);
		D3DXMatrixMultiply (&matWorldViewProj, &tmp, matProj);
		hr = s->pEffect->SetMatrix (s->m_MatWorldViewProjEffectHandle, &matWorldViewProj);
		if (FAILED (hr)) {
			write_log (_T("%s: Create:SetMatrix:matWorldViewProj %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	return 1;
}

static int psEffect_SetTextures (LPDIRECT3DTEXTURE9 lpSource, struct shaderdata *s)
{
	HRESULT hr;
	D3DXVECTOR4 fDims, fTexelSize;

	if (!s->m_SourceTextureEffectHandle) {
		write_log (_T("%s: Texture with SOURCETEXTURE semantic not found\n"), D3DHEAD);
		return 0;
	}
	hr = s->pEffect->SetTexture (s->m_SourceTextureEffectHandle, lpSource);
	if (FAILED (hr)) {
		write_log (_T("%s: SetTextures:lpSource %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	if (s->m_WorkingTexture1EffectHandle) {
		hr = s->pEffect->SetTexture (s->m_WorkingTexture1EffectHandle, s->lpWorkTexture1);
		if (FAILED (hr)) {
			write_log (_T("%s: SetTextures:lpWorking1 %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_WorkingTexture2EffectHandle) {
		hr = s->pEffect->SetTexture (s->m_WorkingTexture2EffectHandle, s->lpWorkTexture2);
		if (FAILED (hr)) {
			write_log (_T("%s: SetTextures:lpWorking2 %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_Hq2xLookupTextureHandle) {
		hr = s->pEffect->SetTexture (s->m_Hq2xLookupTextureHandle, s->lpHq2xLookupTexture);
		if (FAILED (hr)) {
			write_log (_T("%s: SetTextures:lpHq2xLookupTexture %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	fDims.x = 256; fDims.y = 256; fDims.z  = 1; fDims.w = 1;
	fTexelSize.x = 1; fTexelSize.y = 1; fTexelSize.z = 1; fTexelSize.w = 1; 
	if (lpSource) {
		D3DSURFACE_DESC Desc;
		lpSource->GetLevelDesc (0, &Desc);
		fDims.x = (FLOAT) Desc.Width;
		fDims.y = (FLOAT) Desc.Height;
	}

	fTexelSize.x = 1.0f / fDims.x;
	fTexelSize.y = 1.0f / fDims.y;

	if (s->m_SourceDimsEffectHandle) {
		hr = s->pEffect->SetVector(s->m_SourceDimsEffectHandle, &fDims);
		if (FAILED(hr)) {
			write_log(_T("%s: SetTextures:SetVector:Source %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return 0;
		}
	}
	if (s->m_InputDimsEffectHandle) {
		hr = s->pEffect->SetVector(s->m_InputDimsEffectHandle, &fDims);
		if (FAILED(hr)) {
			write_log(_T("%s: SetTextures:SetVector:Input %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return 0;
		}
	}
	if (s->m_TargetDimsEffectHandle) {
		D3DXVECTOR4 fDimsTarget;
		fDimsTarget.x = (FLOAT)s->targettex_width;
		fDimsTarget.y = (FLOAT)s->targettex_height;
		fDimsTarget.z = 1;
		fDimsTarget.w = 1;
		hr = s->pEffect->SetVector(s->m_TargetDimsEffectHandle, &fDimsTarget);
		if (FAILED(hr)) {
			write_log(_T("%s: SetTextures:SetVector:Target %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return 0;
		}
	}
	if (s->m_TexelSizeEffectHandle) {
		hr = s->pEffect->SetVector (s->m_TexelSizeEffectHandle, &fTexelSize);
		if (FAILED (hr)) {
			write_log (_T("%s: SetTextures:SetVector:Texel %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
	}
	if (s->m_ScaleEffectHandle) {
		D3DXVECTOR4 fScale;
		fScale.x = (float)(1 << currprefs.gfx_resolution);
		fScale.y = (float)(1 << currprefs.gfx_vresolution);
		fScale.w = fScale.z = 1;
		hr = s->pEffect->SetVector(s->m_ScaleEffectHandle, &fScale);
		if (FAILED(hr)) {
			write_log(_T("%s: SetTextures:SetVector:Scale %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return 0;
		}
	}
	if (s->framecounterHandle)
		s->pEffect->SetFloat(s->framecounterHandle, (FLOAT)vsync_counter);

	return 1;
}

static int psEffect_Begin (enum psEffect_Pass pass, UINT *pPasses, struct shaderdata *s)
{
	HRESULT hr;
	LPD3DXEFFECT effect = s->pEffect;
	switch (pass)
	{
	case psEffect_PreProcess1:
		hr = effect->SetTechnique (s->m_PreprocessTechnique1EffectHandle);
		break;
	case psEffect_PreProcess2:
		hr = effect->SetTechnique (s->m_PreprocessTechnique2EffectHandle);
		break;
	case psEffect_Combine:
		hr = effect->SetTechnique (s->m_CombineTechniqueEffectHandle);
		break;
	default:
		hr = S_OK;
		break;
	}
	if (FAILED (hr)) {
		write_log (_T("%s: SetTechnique: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	hr = effect->Begin (pPasses, 0);
	if (FAILED (hr)) {
		write_log (_T("%s: Begin: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	return 1;
}

static int psEffect_BeginPass (LPD3DXEFFECT effect, UINT Pass)
{
	HRESULT hr;

	hr = effect->BeginPass (Pass);
	if (FAILED (hr)) {
		write_log (_T("%s: BeginPass: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	return 1;
}
static int psEffect_EndPass (LPD3DXEFFECT effect)
{
	HRESULT hr;

	hr = effect->EndPass ();
	if (FAILED (hr)) {
		write_log (_T("%s: EndPass: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	return 1;
}
static int psEffect_End (LPD3DXEFFECT effect)
{
	HRESULT hr;

	hr = effect->End ();
	if (FAILED (hr)) {
		write_log (_T("%s: End: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return 0;
	}
	return 1;
}

static LPDIRECT3DTEXTURE9 createtext (struct d3dstruct *d3d, int w, int h, D3DFORMAT format)
{
	LPDIRECT3DTEXTURE9 t;
	D3DLOCKED_RECT locked;
	HRESULT hr;

	hr = d3d->d3ddev->CreateTexture (w, h, 1, D3DUSAGE_DYNAMIC, format, D3DPOOL_DEFAULT, &t, NULL);
	if (FAILED (hr))
		write_log (_T("%s: CreateTexture() D3DUSAGE_DYNAMIC failed: %s (%d*%d %08x)\n"),
			D3DHEAD, D3D_ErrorString (hr), w, h, format);
	if (FAILED (hr)) {
		hr = d3d->d3ddev->CreateTexture (w, h, 1, 0, format, D3DPOOL_DEFAULT, &t, NULL);
	}
	if (FAILED (hr)) {
		write_log (_T("%s: CreateTexture() failed: %s (%d*%d %08x)\n"),
			D3DHEAD, D3D_ErrorString (hr), w, h, format);
		return 0;
	}
	hr = t->LockRect (0, &locked, NULL, 0);
	if (SUCCEEDED (hr)) {
		int y;
		int wb;
		wb = w * 4;
		if (wb > locked.Pitch)
			wb = w * 2;
		if (wb > locked.Pitch)
			wb = w * 1;
		for (y = 0; y < h; y++)
			memset ((uae_u8*)locked.pBits + y * locked.Pitch, 0, wb);
		t->UnlockRect (0);
	}
	return t;
}

static int allocextratextures (struct d3dstruct *d3d, struct shaderdata *s, int w, int h)
{
	HRESULT hr;
	if (FAILED (hr = d3d->d3ddev->CreateTexture (w, h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &s->lpWorkTexture1, NULL))) {
		write_log (_T("%s: Failed to create working texture1: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), s - &d3d->shaders[0]);
		return 0;
	}
	if (FAILED (hr = d3d->d3ddev->CreateTexture (w, h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &s->lpWorkTexture2, NULL))) {
		write_log (_T("%s: Failed to create working texture2: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), s - &d3d->shaders[0]);
		return 0;
	}
	write_log (_T("%s: %d*%d working texture:%d\n"), D3DHEAD, w, h, s - &d3d->shaders[0]);
	return 1;
}

static int createamigatexture (struct d3dstruct *d3d, int w, int h)
{
	HRESULT hr;

	d3d->texture1 = createtext(d3d, w, h, d3d->tformat);
	d3d->texture2 = createtext(d3d, w, h, d3d->tformat);
	if (!d3d->texture1 || !d3d->texture2)
		return 0;
	d3d->usetexture = d3d->texture1;
	write_log (_T("%s: %d*%d main texture, depth %d\n"), D3DHEAD, w, h, d3d->t_depth);
	if (d3d->psActive) {
		for (int i = 0; i < MAX_SHADERS; i++) {
			int w2, h2;
			int type = d3d->shaders[i].type;
			if (type == SHADERTYPE_BEFORE) {
				w2 = d3d->shaders[i].worktex_width;
				h2 = d3d->shaders[i].worktex_height;
				if (!allocextratextures (d3d, &d3d->shaders[i], w, h))
					return 0;
			} else if (type == SHADERTYPE_MIDDLE) {
				w2 = d3d->shaders[i].worktex_width;
				h2 = d3d->shaders[i].worktex_height;
			} else {
				w2 = d3d->window_w;
				h2 = d3d->window_h;
			}
			if (type == SHADERTYPE_BEFORE || type == SHADERTYPE_AFTER || type == SHADERTYPE_MIDDLE) {
				D3DLOCKED_BOX lockedBox;
				if (FAILED (hr = d3d->d3ddev->CreateVolumeTexture (256, 16, 256, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d->shaders[i].lpHq2xLookupTexture, NULL))) {
					write_log (_T("%s: Failed to create volume texture: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), i);
					return 0;
				}
				if (FAILED (hr = d3d->shaders[i].lpHq2xLookupTexture->LockBox (0, &lockedBox, NULL, 0))) {
					write_log (_T("%s: Failed to lock box of volume texture: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), i);
					return 0;
				}
				write_log (_T("HQ2X texture (%dx%d) (%dx%d):%d\n"), w2, h2, w, h, i);
				BuildHq2xLookupTexture (w2, h2, w, h,  (unsigned char*)lockedBox.pBits);
				d3d->shaders[i].lpHq2xLookupTexture->UnlockBox (0);
			}
		}
	}
	return 1;
}

static int createtexture (struct d3dstruct *d3d, int ow, int oh, int win_w, int win_h)
{
	HRESULT hr;
	bool haveafter = false;

	int zw, zh;

	if (ow > win_w * d3d->dmultxh && oh > win_h * d3d->dmultxv) {
		zw = ow;
		zh = oh;
	} else {
		zw = win_w * d3d->dmultxh;
		zh = win_h * d3d->dmultxv;
	}
	for (int i = 0; i < MAX_SHADERS; i++) {
		if (d3d->shaders[i].type == SHADERTYPE_BEFORE || d3d->shaders[i].type == SHADERTYPE_AFTER || d3d->shaders[i].type == SHADERTYPE_MIDDLE) {
			int w2, h2, w, h;
			if (d3d->shaders[i].type == SHADERTYPE_AFTER) {
				w2 = zw; h2 = zh;
				w = zw; h = zh;
				haveafter = true;
				if (!allocextratextures (d3d, &d3d->shaders[i], d3d->window_w, d3d->window_h))
					return 0;
			} else if (d3d->shaders[i].type == SHADERTYPE_MIDDLE) {
				// worktex_width = 800
				// extratex = amiga res
				w2 = zw; h2 = zh;
				w = zw; h = zh;
				if (!allocextratextures (d3d, &d3d->shaders[i], ow, oh))
					return 0;
			} else {
				w2 = ow;
				h2 = oh;
				w = ow;
				h = oh;
			}
			d3d->shaders[i].targettex_width = w2;
			d3d->shaders[i].targettex_height = h2;
			if (FAILED (hr = d3d->d3ddev->CreateTexture (w2, h2, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d->shaders[i].lpTempTexture, NULL))) {
				write_log (_T("%s: Failed to create working texture1: %s:%d:%d\n"), D3DHEAD, D3D_ErrorString (hr), i, d3d->shaders[i].type);
				return 0;
			}
			write_log (_T("%s: %d*%d temp texture:%d:%d\n"), D3DHEAD, w2, h2, i, d3d->shaders[i].type);
			d3d->shaders[i].worktex_width = w;
			d3d->shaders[i].worktex_height = h;
		}
	}
	if (haveafter) {
		if (FAILED (hr = d3d->d3ddev->CreateTexture (d3d->window_w, d3d->window_h, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d->lpPostTempTexture, NULL))) {
			write_log (_T("%s: Failed to create temp texture: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
		write_log (_T("%s: %d*%d after texture\n"), D3DHEAD, d3d->window_w, d3d->window_h);
	}
	return 1;
}

static void updateleds (struct d3dstruct *d3d)
{
	D3DLOCKED_RECT locked;
	HRESULT hr;
	static uae_u32 rc[256], gc[256], bc[256], a[256];
	static int done;

	if (!done) {
		for (int i = 0; i < 256; i++) {
			rc[i] = i << 16;
			gc[i] = i << 8;
			bc[i] = i << 0;
			a[i] = i << 24;
		}
		done = 1;
	}

	if (d3d != &d3ddata[0])
		return;

	hr = d3d->ledtexture->LockRect (0, &locked, NULL, D3DLOCK_DISCARD);
	if (FAILED (hr)) {
		write_log (_T("%d: SL LockRect failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return;
	}

	for (int y = 0; y < d3d->ledheight; y++) {
		uae_u8 *buf = (uae_u8*)locked.pBits + y * locked.Pitch;
		statusline_single_erase(d3d->num, buf, 32 / 8, y, d3d->ledwidth);
	}
	statusline_render(d3d->num, (uae_u8*)locked.pBits, 32 / 8, locked.Pitch, d3d->ledwidth, d3d->ledheight, rc, gc, bc, a);

	for (int y = 0; y < d3d->ledheight; y++) {
		uae_u8 *buf = (uae_u8*)locked.pBits + y * locked.Pitch;
		draw_status_line_single(d3d->num, buf, 32 / 8, y, d3d->ledwidth, rc, gc, bc, a);
	}

	d3d->ledtexture->UnlockRect (0);
}

static int createledtexture (struct d3dstruct *d3d)
{
	struct AmigaMonitor *mon = &AMonitors[d3d - d3ddata];

	d3d->statusbar_hx = d3d->statusbar_vx = statusline_set_multiplier(mon->monitor_id, d3d->tout_w, d3d->tout_h) / 100;
	d3d->ledwidth = d3d->window_w;
	d3d->ledheight = TD_TOTAL_HEIGHT * d3d->statusbar_vx;
	d3d->ledtexture = createtext(d3d, d3d->ledwidth, d3d->ledheight * d3d->statusbar_vx, D3DFMT_A8R8G8B8);
	if (!d3d->ledtexture)
		return 0;
	return 1;
}

static int createsltexture (struct d3dstruct *d3d)
{
	d3d->sltexture = createtext (d3d, d3d->required_sl_texture_w, d3d->required_sl_texture_h, d3d->t_depth < 32 ? D3DFMT_A4R4G4B4 : D3DFMT_A8R8G8B8);
	if (!d3d->sltexture)
		return 0;
	write_log (_T("%s: SL %d*%d texture allocated\n"), D3DHEAD, d3d->required_sl_texture_w, d3d->required_sl_texture_h);
	d3d->maskmult_x = 1.0f;
	d3d->maskmult_y = 1.0f;
	return 1;
}

static void createscanlines (struct d3dstruct *d3d, int force)
{
	HRESULT hr;
	D3DLOCKED_RECT locked;
	int sl4, sl42;
	int l1, l2;
	int x, y, yy;
	uae_u8 *sld, *p;
	int bpp;

	if (d3d->scanline_osl1 == d3d->filterd3d->gfx_filter_scanlines &&
		d3d->scanline_osl3 == d3d->filterd3d->gfx_filter_scanlinelevel &&
		d3d->scanline_osl2 == d3d->filterd3d->gfx_filter_scanlineratio &&
		d3d->scanline_osl4 == d3d->filterd3d->gfx_filter_scanlineoffset &&
		!force)
		return;
	bpp = d3d->t_depth < 32 ? 2 : 4;
	d3d->scanline_osl1 = d3d->filterd3d->gfx_filter_scanlines;
	d3d->scanline_osl3 = d3d->filterd3d->gfx_filter_scanlinelevel;
	d3d->scanline_osl2 = d3d->filterd3d->gfx_filter_scanlineratio;
	d3d->scanline_osl4 = d3d->filterd3d->gfx_filter_scanlineoffset;
	sl4 = d3d->filterd3d->gfx_filter_scanlines * 16 / 100;
	sl42 = d3d->filterd3d->gfx_filter_scanlinelevel * 16 / 100;
	if (sl4 > 15)
		sl4 = 15;
	if (sl42 > 15)
		sl42 = 15;
	l1 = (d3d->filterd3d->gfx_filter_scanlineratio >> 0) & 15;
	l2 = (d3d->filterd3d->gfx_filter_scanlineratio >> 4) & 15;

	if (l1 + l2 <= 0)
		return;

	if (!d3d->sltexture) {
		if (d3d->scanline_osl1 == 0 && d3d->scanline_osl3 == 0)
			return;
		if (!createsltexture (d3d))
			return;
	}

	hr = d3d->sltexture->LockRect (0, &locked, NULL, 0);
	if (FAILED (hr)) {
		write_log (_T("%s: SL LockRect failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return;
	}
	sld = (uae_u8*)locked.pBits;
	for (y = 0; y < d3d->required_sl_texture_h; y++) {
		memset(sld + y * locked.Pitch, 0, d3d->required_sl_texture_w * bpp);
	}
	for (y = 0; y < d3d->required_sl_texture_h; y += l1 + l2) {
		int y2 = y + (d3d->filterd3d->gfx_filter_scanlineoffset % (l1 + 1));
		for (yy = 0; yy < l2 && y2 + yy < d3d->required_sl_texture_h; yy++) {
			for (x = 0; x < d3d->required_sl_texture_w; x++) {
				uae_u8 sll = sl42;
				p = &sld[(y2 + yy) * locked.Pitch + (x * bpp)];
				if (bpp < 4) {
					/* 16-bit, A4R4G4B4 */
					p[1] = (sl4 << 4) | (sll << 0);
					p[0] = (sll << 4) | (sll << 0);
				} else {
					/* 32-bit, A8R8G8B8 */
					uae_u8 sll4 = sl4 | (sl4 << 4);
					uae_u8 sll2 = sll | (sll << 4);
					p[0] = sll2;
					p[1] = sll2;
					p[2] = sll2;
					p[3] = sll4;
				}
			}
		}
	}
	d3d->sltexture->UnlockRect (0);
}

#include "png.h"

struct uae_image
{
	uae_u8 *data;
	int width, height, pitch;
};

struct png_cb
{
	uae_u8 *ptr;
	size_t size;
};

static void __cdecl readcallback(png_structp png_ptr, png_bytep out, png_size_t count)
{
	png_voidp io_ptr = png_get_io_ptr(png_ptr);

	if (!io_ptr)
		return;
	struct png_cb *cb = (struct png_cb*)io_ptr;
	if (count > cb->size)
		count = cb->size;
	memcpy(out, cb->ptr, count);
	cb->ptr += count;
	cb->size -= count;
}

static bool load_png_image(struct zfile *zf, struct uae_image *img)
{
	extern unsigned char test_card_png[];
	extern unsigned int test_card_png_len;
	uae_u8 *b = test_card_png;
	uae_u8 *bfree = NULL;
	png_structp png_ptr;
	png_infop info_ptr;
	png_uint_32 width, height;
	int depth, color_type;
	struct png_cb cb;
	png_bytepp row_pp;
	png_size_t cols;
	bool ok = false;

	memset(img, 0, sizeof(struct uae_image));
	int size;
	uae_u8 *bb = zfile_getdata(zf, 0, -1, &size);
	if (!bb)
		goto end;
	b = bb;
	bfree = bb;

	if (!png_check_sig(b, 8))
		goto end;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
	if (!png_ptr)
		goto end;
	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, 0, 0);
		goto end;
	}
	cb.ptr = b;
	cb.size = size;
	png_set_read_fn(png_ptr, &cb, readcallback);

	png_read_info(png_ptr, info_ptr);

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &depth, &color_type, 0, 0, 0);

	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_expand(png_ptr);
	if (color_type == PNG_COLOR_TYPE_GRAY && depth < 8)
		png_set_expand(png_ptr);

	if (depth > 8)
		png_set_strip_16(png_ptr);
	if (depth < 8)
		png_set_packing(png_ptr);
	if (!(color_type & PNG_COLOR_MASK_ALPHA))
		png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);

	png_set_bgr(png_ptr);

	cols = png_get_rowbytes(png_ptr, info_ptr);

	img->pitch = width * 4;
	img->width = width;
	img->height = height;

	row_pp = new png_bytep[height];

	img->data = xcalloc(uae_u8, width * height * 4);

	for (int i = 0; i < height; i++) {
		row_pp[i] = (png_bytep)&img->data[i * img->pitch];
	}

	png_read_image(png_ptr, row_pp);
	png_read_end(png_ptr, info_ptr);

	png_destroy_read_struct(&png_ptr, &info_ptr, 0);

	delete[] row_pp;

	ok = true;
end:
	xfree(bfree);

	return ok;
}

static void free_uae_image(struct uae_image *img)
{
	if (!img)
		return;
	xfree(img->data);
	img->data = NULL;
}

static int findedge(struct uae_image *img, int w, int h, int dx, int dy)
{
	int x = w / 2;
	int y = h / 2;

	if (dx != 0)
		x = dx < 0 ? 0 : w - 1;
	if (dy != 0)
		y = dy < 0 ? 0 : h - 1;

	for (;;) {
		uae_u32 *p = (uae_u32*)(img->data + y * img->pitch + x * 4);
		int alpha = (*p) >> 24;
		if (alpha != 255)
			break;
		x -= dx;
		y -= dy;
		if (x <= 0 || y <= 0)
			break;
		if (x >= w - 1 || y >= h - 1)
			break;
	}
	if (dx)
		return x;
	return y;
}

static void narrowimg(struct uae_image *img, int *xop, int *yop, const TCHAR *name)
{
	int x1, x2, y1, y2;

	for (y1 = 0; y1 < img->height; y1++) {
		bool tline = true;
		for (int x = 0; x < img->width; x++) {
			uae_u8 *p = img->data + y1 * img->pitch + x * 4;
			if (p[3] != 0x00)
				tline = false;
		}
		if (!tline)
			break;
	}

	for (y2 = img->height - 1; y2 >= y1; y2--) {
		bool tline = true;
		for (int x = 0; x < img->width; x++) {
			uae_u8 *p = img->data + y2 * img->pitch + x * 4;
			if (p[3] != 0x00)
				tline = false;
		}
		if (!tline)
			break;
	}

	for (x1 = 0; x1 < img->width; x1++) {
		bool tline = true;
		for (int y = y1; y <= y2; y++) {
			uae_u8 *p = img->data + y * img->pitch + x1 * 4;
			if (p[3] != 0x00)
				tline = false;
		}
		if (!tline)
			break;
	}

	for (x2 = img->width - 1; x2 >= x1; x2--) {
		bool tline = true;
		for (int y = y1; y <= y2; y++) {
			uae_u8 *p = img->data + y * img->pitch + x2 * 4;
			if (p[3] != 0x00)
				tline = false;
		}
		if (!tline)
			break;
	}

	int w = x2 - x1 + 1;
	int h = y2 - y1 + 1;
	int pitch = w * 4;

	*xop = x1;
	*yop = y1;

	uae_u8 *d = xcalloc(uae_u8, img->width * img->height * 4);
	for (int y = 0; y < h; y++) {
		uae_u8 *dp = d + y * pitch;
		uae_u8 *sp = img->data + (y + y1) * img->pitch + x1 * 4;
		memcpy(dp, sp, w * 4);
	}

	write_log(_T("Overlay LED: '%s' %d*%d -> %d*%d (%d*%d - %d*%d)\n"), name, img->width, img->height, w, h, x1, y1, x2, y2);

	xfree(img->data);
	img->width = w;
	img->height = h;
	img->pitch = pitch;
	img->data = d;
}

static uae_u8 dimming(uae_u8 v)
{
	return v * currprefs.power_led_dim / 100;
}

static int createmask2texture (struct d3dstruct *d3d, const TCHAR *filename)
{
	struct AmigaMonitor *mon = &AMonitors[d3d - d3ddata];
	struct zfile *zf;
	LPDIRECT3DTEXTURE9 tx;
	HRESULT hr;
	TCHAR tmp[MAX_DPATH];
	TCHAR filepath[MAX_DPATH];
	D3DLOCKED_RECT locked;
	float xmult, ymult;

	if (d3d->mask2texture)
		d3d->mask2texture->Release();
	d3d->mask2texture = NULL;
	for (int i = 0; overlayleds[i]; i++) {
		if (d3d->mask2textureleds[i])
			d3d->mask2textureleds[i]->Release();
		d3d->mask2textureleds[i] = NULL;
	}
	if (d3d->mask2textureled_power_dim)
		d3d->mask2textureled_power_dim->Release();
	d3d->mask2textureled_power_dim = NULL;

	if (filename[0] == 0)
		return 0;

	zf = NULL;
	for (int i = 0; i < 2; i++) {
		if (i == 0) {
			get_plugin_path (tmp, sizeof tmp / sizeof (TCHAR), _T("overlays"));
			_tcscat (tmp, filename);
		} else {
			_tcscpy (tmp, filename);
		}
		TCHAR tmp2[MAX_DPATH], tmp3[MAX_DPATH];
		_tcscpy (tmp3, tmp);
		TCHAR *s = _tcsrchr (tmp3, '.');
		if (s) {
			TCHAR *s2 = s;
			while (s2 > tmp3) {
				TCHAR v = *s2;
				if (v == '_') {
					s = s2;
					break;
				}
				if (v == 'X' || v == 'x') {
					s2--;
					continue;
				}
				if (!_istdigit (v))
					break;
				s2--;
			}
			_tcscpy (tmp2, s);
			_stprintf (s, _T("_%dx%d%s"), d3d->window_w, d3d->window_h, tmp2);
			_tcscpy(filepath, tmp3);
			zf = zfile_fopen (tmp3, _T("rb"), ZFD_NORMAL);
			if (zf)
				break;
			float aspect = (float)d3d->window_w / d3d->window_h;
			int ax = -1, ay = -1;
			if (abs (aspect - 16.0 / 10.0) <= 0.1)
				ax = 16, ay = 10;
			if (abs (aspect - 16.0 / 9.0) <= 0.1)
				ax = 16, ay = 9;
			if (abs (aspect - 4.0 / 3.0) <= 0.1)
				ax = 4, ay = 3;
			if (ax > 0 && ay > 0) {
				_stprintf (s, _T("_%dx%d%s"), ax, ay, tmp2);
				_tcscpy(filepath, tmp3);
				zf = zfile_fopen (tmp3, _T("rb"), ZFD_NORMAL);
				if (zf)
					break;
			}
		}
		_tcscpy(filepath, tmp);
		zf = zfile_fopen (tmp, _T("rb"), ZFD_NORMAL);
		if (zf)
			break;
	}
	if (!zf) {
		write_log (_T("%s: couldn't open overlay '%s'\n"), D3DHEAD, filename);
		return 0;
	}
	struct uae_image img;
	if (!load_png_image(zf, &img)) {
		write_log(_T("Overlay texture '%s' load failed.\n"), filename);
		goto end;
	}

	tx = createtext(d3d, img.width, img.height, D3DFMT_A8R8G8B8);
	if (!tx) {
		write_log(_T("%s: overlay texture load failed.\n"), D3DHEAD);
		goto end;
	}

	d3d->mask2texture_w = (float)img.width;
	d3d->mask2texture_h = (float)img.height;
	d3d->mask2texture = tx;

	hr = tx->LockRect(0, &locked, NULL, 0);
	if (FAILED(hr)) {
		write_log(_T("%s: Overlay LockRect failed: %s\n"), D3DHEAD, D3D_ErrorString(hr));
		goto end;
	}
	for (int i = 0; i < img.height; i++) {
		memcpy((uae_u8*)locked.pBits + i * locked.Pitch, img.data + i * img.pitch, img.width * 4);
	}
	tx->UnlockRect(0);

	d3d->mask2rect.left = findedge(&img, (int)d3d->mask2texture_w, (int)d3d->mask2texture_h, -1, 0);
	d3d->mask2rect.right = findedge(&img, (int)d3d->mask2texture_w, (int)d3d->mask2texture_h, 1, 0);
	d3d->mask2rect.top = findedge(&img, (int)d3d->mask2texture_w, (int)d3d->mask2texture_h, 0, -1);
	d3d->mask2rect.bottom = findedge(&img, (int)d3d->mask2texture_w, (int)d3d->mask2texture_h, 0, 1);

	if (d3d->mask2rect.left >= d3d->mask2texture_w / 2 || d3d->mask2rect.top >= d3d->mask2texture_h / 2 ||
		d3d->mask2rect.right <= d3d->mask2texture_w / 2 || d3d->mask2rect.bottom <= d3d->mask2texture_h / 2) {
		d3d->mask2rect.left = 0;
		d3d->mask2rect.top = 0;
		d3d->mask2rect.right = (LONG)d3d->mask2texture_w;
		d3d->mask2rect.bottom = (LONG)d3d->mask2texture_h;
	}
	d3d->mask2texture_multx = (float)d3d->window_w / d3d->mask2texture_w;
	d3d->mask2texture_multy = (float)d3d->window_h / d3d->mask2texture_h;
	d3d->mask2texture_offsetw = 0;

	if (isfullscreen () > 0) {
		struct MultiDisplay *md = getdisplay(&currprefs, mon->monitor_id);
		float deskw = (float)md->rect.right - md->rect.left;
		float deskh = (float)md->rect.bottom - md->rect.top;
		//deskw = 800; deskh = 600;
		float dstratio = deskw / deskh;
		float srcratio = d3d->mask2texture_w / d3d->mask2texture_h;
		d3d->mask2texture_multx *= srcratio / dstratio;
	} else {
		d3d->mask2texture_multx = d3d->mask2texture_multy;
	}

	d3d->mask2texture_wh = (float)d3d->window_h;
	d3d->mask2texture_ww = d3d->mask2texture_w * d3d->mask2texture_multx; 

	d3d->mask2texture_offsetw = (d3d->window_w - d3d->mask2texture_ww) / 2;

	if (d3d->mask2texture_offsetw > 0)
		d3d->blanktexture = createtext(d3d, (int)(d3d->mask2texture_offsetw + 1), d3d->window_h, D3DFMT_X8R8G8B8);

	xmult = d3d->mask2texture_multx;
	ymult = d3d->mask2texture_multy;

	d3d->mask2rect.left = (LONG)(d3d->mask2rect.left * xmult);
	d3d->mask2rect.right = (LONG)(d3d->mask2rect.right * xmult);
	d3d->mask2rect.top = (LONG)(d3d->mask2rect.top * ymult);
	d3d->mask2rect.bottom = (LONG)(d3d->mask2rect.bottom * ymult);
	d3d->mask2texture_wwx = d3d->mask2texture_w * xmult;
	if (d3d->mask2texture_wwx > d3d->window_w)
		d3d->mask2texture_wwx = (float)d3d->window_w;
	if (d3d->mask2texture_wwx < d3d->mask2rect.right - d3d->mask2rect.left)
		d3d->mask2texture_wwx = (float)d3d->mask2rect.right - d3d->mask2rect.left;
	if (d3d->mask2texture_wwx > d3d->mask2texture_ww)
		d3d->mask2texture_wwx = d3d->mask2texture_ww;

	d3d->mask2texture_minusx = (float)(-((d3d->window_w - d3d->mask2rect.right) + d3d->mask2rect.left));
	if (d3d->mask2texture_offsetw > 0)
		d3d->mask2texture_minusx += d3d->mask2texture_offsetw * xmult;
	

	d3d->mask2texture_minusy = (float)(-(d3d->window_h - (d3d->mask2rect.bottom - d3d->mask2rect.top)));

	d3d->mask2texture_hhx = d3d->mask2texture_h * ymult;

	write_log (_T("%s: overlay '%s' %.0f*%.0f (%d*%d - %d*%d) (%d*%d)\n"),
		D3DHEAD, tmp, d3d->mask2texture_w, d3d->mask2texture_h,
		d3d->mask2rect.left, d3d->mask2rect.top, d3d->mask2rect.right, d3d->mask2rect.bottom,
		d3d->mask2rect.right - d3d->mask2rect.left, d3d->mask2rect.bottom - d3d->mask2rect.top);

	for (int i = 0; overlayleds[i]; i++) {
		if (!overlayleds[i][0])
			continue;
		TCHAR tmp1[MAX_DPATH];
		_tcscpy(tmp1, filepath);
		_tcscpy(tmp1 + _tcslen(tmp1) - 4, _T("_"));
		_tcscpy(tmp1 + _tcslen(tmp1), overlayleds[i]);
		_tcscat(tmp1, _T("_led"));
		_tcscat(tmp1, filepath + _tcslen(filepath) - 4);
		zf = zfile_fopen(tmp1, _T("rb"), ZFD_NORMAL);
		if (zf) {
			struct uae_image ledimg;
			if (load_png_image(zf, &ledimg)) {
				if (ledimg.width == img.width && ledimg.height == img.height) {
					narrowimg(&ledimg, &d3d->mask2textureledoffsets[i * 2 + 0], &d3d->mask2textureledoffsets[i * 2 + 1], tmp1);
					d3d->mask2textureleds[i] = createtext(d3d, ledimg.width, ledimg.height, D3DFMT_A8R8G8B8);
					if (d3d->mask2textureleds[i]) {
						hr = d3d->mask2textureleds[i]->LockRect(0, &locked, NULL, 0);
						if (SUCCEEDED(hr)) {
							for (int j = 0; j < ledimg.height; j++) {
								memcpy((uae_u8*)locked.pBits + j * locked.Pitch, ledimg.data + j * ledimg.pitch, ledimg.width * 4);
							}
							d3d->mask2textureleds[i]->UnlockRect(0);
						}
					}
					if (ledtypes[i] == LED_POWER) {
						d3d->mask2textureled_power_dim = createtext(d3d, ledimg.width, ledimg.height, D3DFMT_A8R8G8B8);
						if (d3d->mask2textureled_power_dim) {
							hr = d3d->mask2textureled_power_dim->LockRect(0, &locked, NULL, 0);
							if (SUCCEEDED(hr)) {
								for (int j = 0; j < ledimg.height; j++) {
									uae_u8 *pd = (uae_u8*)locked.pBits + j * locked.Pitch;
									uae_u8 *ps = ledimg.data + j * ledimg.pitch;
									for (int k = 0; k < ledimg.width; k++) {
										pd[0] = dimming(ps[0]);
										pd[1] = dimming(ps[1]);
										pd[2] = dimming(ps[2]);
										pd[3] = ps[3];
										pd += 4;
										ps += 4;
									}
								}
								d3d->mask2textureled_power_dim->UnlockRect(0);
							}
						}
					}
				} else {
					write_log(_T("Overlay led '%s' size mismatch.\n"), tmp1);
				}
				free_uae_image(&ledimg);
			} else {
				write_log(_T("Overlay led '%s' load failed.\n"), tmp1);
			}
			zfile_fclose(zf);
		}
	}

	free_uae_image(&img);

	return 1;
end:
	free_uae_image(&img);
	if (tx)
		tx->Release ();
	if (d3d->blanktexture)
		d3d->blanktexture->Release();
	d3d->blanktexture = NULL;
	return 0;
}

static int createmasktexture (struct d3dstruct *d3d, const TCHAR *filename, struct shaderdata *sd)
{
	struct zfile *zf;
	int size;
	uae_u8 *buf;
	D3DSURFACE_DESC maskdesc, txdesc;
	LPDIRECT3DTEXTURE9 tx;
	HRESULT hr;
	D3DLOCKED_RECT lock, slock;
	D3DXIMAGE_INFO dinfo;
	TCHAR tmp[MAX_DPATH];
	int maskwidth, maskheight;
	int idx = addrdiff(sd, &d3d->shaders[0]);

	if (filename[0] == 0)
		return 0;
	tx = NULL;
	get_plugin_path (tmp, sizeof tmp / sizeof (TCHAR), _T("masks"));
	_tcscat (tmp, filename);
	zf = zfile_fopen (tmp, _T("rb"), ZFD_NORMAL);
	if (!zf) {
		zf = zfile_fopen (filename, _T("rb"), ZFD_NORMAL);
		if (!zf) {
			write_log (_T("%s: couldn't open mask '%s':%d\n"), D3DHEAD, filename, idx);
			return 0;
		}
	}
	size = zfile_size32(zf);
	buf = xmalloc(uae_u8, size);
	zfile_fread(buf, size, 1, zf);
	zfile_fclose(zf);
	hr = D3DXCreateTextureFromFileInMemoryEx (d3d->d3ddev, buf, size,
		 D3DX_DEFAULT_NONPOW2, D3DX_DEFAULT_NONPOW2, 1, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8,
		 D3DPOOL_DEFAULT, D3DX_FILTER_NONE, D3DX_FILTER_NONE, 0, &dinfo, NULL, &tx);
	xfree (buf);
	if (FAILED (hr)) {
		write_log (_T("%s: temp mask texture load failed: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), idx);
		goto end;
	}
	hr = tx->GetLevelDesc (0, &txdesc);
	if (FAILED (hr)) {
		write_log (_T("%s: mask image texture GetLevelDesc() failed: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), idx);
		goto end;
	}
	sd->masktexture_w = dinfo.Width;
	sd->masktexture_h = dinfo.Height;
#if 0
	if (txdesc.Width == masktexture_w && txdesc.Height == masktexture_h && psEnabled) {
		// texture size == image size, no need to tile it (Wrap sampler does the rest)
		if (masktexture_w < window_w || masktexture_h < window_h) {
			maskwidth = window_w;
			maskheight = window_h;
		} else {
			masktexture = tx;
			tx = NULL;
		}
	} else {
#endif
		// both must be divisible by mask size
		maskwidth = ((d3d->window_w + sd->masktexture_w - 1) / sd->masktexture_w) * sd->masktexture_w;
		maskheight = ((d3d->window_h + sd->masktexture_h - 1) / sd->masktexture_h) * sd->masktexture_h;
#if 0
	}
#endif
	if (tx) {
		sd->masktexture = createtext (d3d, maskwidth, maskheight, D3DFMT_X8R8G8B8);
		if (FAILED (hr)) {
			write_log (_T("%s: mask texture creation failed: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), idx);
			goto end;
		}
		hr = sd->masktexture->GetLevelDesc (0, &maskdesc);
		if (FAILED (hr)) {
			write_log (_T("%s: mask texture GetLevelDesc() failed: %s:%d\n"), D3DHEAD, D3D_ErrorString (hr), idx);
			goto end;
		}
		if (SUCCEEDED (hr = sd->masktexture->LockRect (0, &lock, NULL, 0))) {
			if (SUCCEEDED (hr = tx->LockRect (0, &slock, NULL, 0))) {
				int x, y, sx, sy;
				uae_u32 *sptr, *ptr;
				sy = 0;
				for (y = 0; y < maskdesc.Height; y++) {
					sx = 0;
					for (x = 0; x < maskdesc.Width; x++) {
						uae_u32 v;
						sptr = (uae_u32*)((uae_u8*)slock.pBits + sy * slock.Pitch + sx * 4);
						ptr = (uae_u32*)((uae_u8*)lock.pBits + y * lock.Pitch + x * 4);
						v = *sptr;
	//					v &= 0x00FFFFFF;
	//					v |= 0x80000000;
						*ptr = v;
						sx++;
						if (sx >= dinfo.Width)
							sx = 0;
					}
					sy++;
					if (sy >= dinfo.Height)
						sy = 0;
				}
				tx->UnlockRect (0);
			}
			sd->masktexture->UnlockRect (0);
		}
		tx->Release ();
		sd->masktexture_w = maskdesc.Width;
		sd->masktexture_h = maskdesc.Height;
	}
	write_log (_T("%s: mask %d*%d (%d*%d) %d*%d ('%s':%d) texture allocated\n"), D3DHEAD, sd->masktexture_w, sd->masktexture_h, txdesc.Width, txdesc.Height, maskdesc.Width, maskdesc.Height, filename, idx);
	d3d->maskmult_x = (float)d3d->window_w / sd->masktexture_w;
	d3d->maskmult_y = (float)d3d->window_h / sd->masktexture_h;

	return 1;
end:
	if (sd->masktexture)
		sd->masktexture->Release ();
	sd->masktexture = NULL;
	if (tx)
		tx->Release ();
	return 0;
}

static bool xD3D_getscalerect(int monid, float *mx, float *my, float *sx, float *sy, int width, int height)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	struct vidbuf_description *vidinfo = &adisplays[monid].gfxvidinfo;

	if (!d3d->mask2texture)
		return false;

	float mw = (float)d3d->mask2rect.right - d3d->mask2rect.left;
	float mh = (float)d3d->mask2rect.bottom - d3d->mask2rect.top;

	float mxt = (float)mw / width;
	float myt = (float)mh / height;

	*mx = d3d->mask2texture_minusx / mxt;
	*my = d3d->mask2texture_minusy / myt;

	*sx = -((d3d->mask2texture_ww - d3d->mask2rect.right) - (d3d->mask2rect.left)) / 2;
	*sy = -((d3d->mask2texture_wh - d3d->mask2rect.bottom) - (d3d->mask2rect.top)) / 2;

	*sx /= mxt;
	*sy /= myt;

	return true;
}

static void setupscenecoords(struct d3dstruct *d3d, bool normalrender, int monid)
{
	struct vidbuf_description *vidinfo = &adisplays[monid].gfxvidinfo;
	RECT sr, dr, zr;
	float w, h;
	float dw, dh;
	static RECT sr2[MAX_AMIGAMONITORS], dr2[MAX_AMIGAMONITORS], zr2[MAX_AMIGAMONITORS];

	if (!normalrender)
		return;

	getfilterrect2 (monid, &dr, &sr, &zr, d3d->window_w, d3d->window_h, d3d->tin_w / d3d->dmult, d3d->tin_h / d3d->dmult, d3d->dmult, &d3d->dmode, d3d->tin_w, d3d->tin_h);

	if (memcmp (&sr, &sr2[monid], sizeof RECT) || memcmp (&dr, &dr2[monid], sizeof RECT) || memcmp (&zr, &zr2[monid], sizeof RECT)) {
		write_log (_T("POS (%d %d %d %d) - (%d %d %d %d)[%d,%d] (%d %d)\n"),
			dr.left, dr.top, dr.right, dr.bottom, sr.left, sr.top, sr.right, sr.bottom,
			sr.right - sr.left, sr.bottom - sr.top,
			zr.left, zr.top);
		sr2[monid] = sr;
		dr2[monid] = dr;
		zr2[monid] = zr;
	}

	d3d->sr2 = sr;
	d3d->dr2 = dr;
	d3d->zr2 = zr;

	dw = (float)dr.right - dr.left;
	dh = (float)dr.bottom - dr.top;
	w = (float)sr.right - sr.left;
	h = (float)sr.bottom - sr.top;

	d3d->fakesize.x = w;
	d3d->fakesize.y = h;
	d3d->fakesize.w = 1;
	d3d->fakesize.z = 1;

	MatrixOrthoOffCenterLH (&d3d->m_matProj_out, 0, w + 0.05f, 0, h + 0.05f, 0.0f, 1.0f);

	float tx, ty;
	float sw, sh;

	if (0 && d3d->mask2texture) {

		float mw = (float)d3d->mask2rect.right - d3d->mask2rect.left;
		float mh = (float)d3d->mask2rect.bottom - d3d->mask2rect.top;

		tx = -0.5f + dw * d3d->tin_w / mw / 2;
		ty = +0.5f + dh * d3d->tin_h / mh / 2;

		float xshift = (float)-zr.left;
		float yshift = (float)-zr.top;

		sw = dw * d3d->tin_w / vidinfo->outbuffer->inwidth2;
		sw *= mw / d3d->window_w;

		tx = -0.5f + d3d->window_w / 2;

		sh = dh * d3d->tin_h / vidinfo->outbuffer->inheight2;
		sh *= mh / d3d->window_h;

		ty = +0.5f + d3d->window_h / 2;

		tx += xshift;
		ty += yshift;

	} else {

		tx = -0.5f + dw * d3d->tin_w / d3d->window_w / 2;
		ty = +0.5f + dh * d3d->tin_h / d3d->window_h / 2;

		float xshift = (float)(- zr.left - sr.left); // - (tin_w - 2 * zr.left - w),
		float yshift = (float)(+ zr.top + sr.top - (d3d->tin_h - h));
	
		sw = dw * d3d->tin_w / d3d->window_w;
		sh = dh * d3d->tin_h / d3d->window_h;

		tx += xshift;
		ty += yshift;

	}

	d3d->xmult = filterrectmult(d3d->window_w, w, d3d->dmode);
	d3d->ymult = filterrectmult(d3d->window_h, h, d3d->dmode);

	MatrixTranslation (&d3d->m_matView_out, tx, ty, 1.0f);

	MatrixScaling (&d3d->m_matWorld_out, sw, sh, 1.0f);

	struct amigadisplay *ad = &adisplays[monid];
	int rota = currprefs.gf[ad->picasso_on ? GF_RTG : ad->interlace_on ? GF_INTERLACE : GF_NORMAL].gfx_filter_rotation;
	if (rota) {
		const float PI = 3.14159265358979f;
		D3DXMATRIXA16 mrot;
		D3DXMatrixRotationZ(&mrot, PI / 180.0f * rota);
		D3DXMATRIXA16 tmprmatrix;
		D3DXMatrixMultiply(&tmprmatrix, &d3d->m_matWorld_out, &mrot);
		d3d->m_matWorld_out = tmprmatrix;
	}

	d3d->cursor_offset_x = -zr.left;
	d3d->cursor_offset_y = -zr.top;

	//write_log (_T("%.1fx%.1f %.1fx%.1f %.1fx%.1f\n"), dw, dh, w, h, sw, sh);

	// ratio between Amiga texture and overlay mask texture
	float sw2 = dw * d3d->tin_w / d3d->window_w;
	float sh2 = dh * d3d->tin_h / d3d->window_h;

	d3d->maskmult.x = sw2 * d3d->maskmult_x / w;
	d3d->maskmult.y = sh2 * d3d->maskmult_y / h;

	d3d->maskshift.x = 1.0f / d3d->maskmult_x;
	d3d->maskshift.y = 1.0f / d3d->maskmult_y;

	D3DXMATRIXA16 tmpmatrix;
	D3DXMatrixMultiply (&tmpmatrix, &d3d->m_matWorld_out, &d3d->m_matView_out);
	D3DXMatrixMultiply (&d3d->postproj, &tmpmatrix, &d3d->m_matProj_out);
}

#if 0
uae_u8 *getfilterbuffer3d(struct vidbuffer *vb, int *widthp, int *heightp, int *pitch, int *depth)
{
	struct d3dstruct *d3d = &d3ddata[0];
	RECT dr, sr, zr;
	uae_u8 *p;
	int w, h;

	*depth = d3d->t_depth;
	getfilterrect2 (&dr, &sr, &zr, d3d->window_w, d3d->window_h, d3d->tin_w / d3d->dmult, d3d->tin_h / d3d->dmult, d3d->dmult, d3d->tin_w, d3d->tin_h);
	w = sr.right - sr.left;
	h = sr.bottom - sr.top;
	p = vb->bufmem;
	if (pitch)
		*pitch = vb->rowbytes;
	p += (zr.top - h / 2) * vb->rowbytes + (zr.left - w / 2) * d3d->t_depth / 8;
	*widthp = w;
	*heightp = h;
	return p;
}
#endif

static void createvertex (struct d3dstruct *d3d)
{
	HRESULT hr;
	struct TLVERTEX *vertices;
	float sizex, sizey;

	sizex = 1.0f;
	sizey = 1.0f;
	if (FAILED (hr = d3d->vertexBuffer->Lock (0, 0, (void**)&vertices, 0))) {
		write_log (_T("%s: Vertexbuffer lock failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return;
	}
	memset (vertices, 0, sizeof (struct TLVERTEX) * NUMVERTICES);
	//Setup vertices
	vertices[0].position.x = -0.5f; vertices[0].position.y = -0.5f;
	vertices[0].diffuse  = 0xFFFFFFFF;
	vertices[0].texcoord.x = 0.0f; vertices[0].texcoord.y = sizey;
	vertices[1].position.x = -0.5f; vertices[1].position.y = 0.5f;
	vertices[1].diffuse  = 0xFFFFFFFF;
	vertices[1].texcoord.x = 0.0f; vertices[1].texcoord.y = 0.0f;
	vertices[2].position.x = 0.5f; vertices[2].position.y = -0.5f;
	vertices[2].diffuse  = 0xFFFFFFFF;
	vertices[2].texcoord.x = sizex; vertices[2].texcoord.y = sizey;
	vertices[3].position.x = 0.5f; vertices[3].position.y = 0.5f;
	vertices[3].diffuse  = 0xFFFFFFFF;
	vertices[3].texcoord.x = sizex; vertices[3].texcoord.y = 0.0f;
	// Additional vertices required for some PS effects
	vertices[4].position.x = 0.0f; vertices[4].position.y = 0.0f;
	vertices[4].diffuse  = 0xFFFFFF00;
	vertices[4].texcoord.x = 0.0f; vertices[4].texcoord.y = 1.0f;
	vertices[5].position.x = 0.0f; vertices[5].position.y = 1.0f;
	vertices[5].diffuse  = 0xFFFFFF00;
	vertices[5].texcoord.x = 0.0f; vertices[5].texcoord.y = 0.0f;
	vertices[6].position.x = 1.0f; vertices[6].position.y = 0.0f;
	vertices[6].diffuse  = 0xFFFFFF00;
	vertices[6].texcoord.x = 1.0f; vertices[6].texcoord.y = 1.0f;
	vertices[7].position.x = 1.0f; vertices[7].position.y = 1.0f;
	vertices[7].diffuse  = 0xFFFFFF00;
	vertices[7].texcoord.x = 1.0f; vertices[7].texcoord.y = 0.0f;
	if (FAILED(hr = d3d->vertexBuffer->Unlock ()))
		write_log (_T("%s: Vertexbuffer unlock failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
}

static void settransform_pre (struct d3dstruct *d3d, struct shaderdata *s)
{
	// Projection is (0,0,0) -> (1,1,1)
	MatrixOrthoOffCenterLH (&d3d->m_matProj, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	// Align texels with pixels
	MatrixTranslation (&d3d->m_matView, -0.5f / d3d->tout_w, 0.5f / d3d->tout_h, 0.0f);
	// Identity for world
	D3DXMatrixIdentity (&d3d->m_matWorld);
}

static void settransform (struct d3dstruct *d3d, struct shaderdata *s)
{
	// Projection is (0,0,0) -> (1,1,1)
	MatrixOrthoOffCenterLH (&d3d->m_matPreProj, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	// Align texels with pixels
	MatrixTranslation (&d3d->m_matPreView, -0.5f / d3d->tout_w, 0.5f / d3d->tout_h, 0.0f);
	// Identity for world
	D3DXMatrixIdentity (&d3d->m_matPreWorld);

	if (s)
		psEffect_SetMatrices (&d3d->m_matProj, &d3d->m_matView, &d3d->m_matWorld, s);

	MatrixOrthoOffCenterLH (&d3d->m_matProj2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);

	MatrixTranslation (&d3d->m_matView2, 0.5f - 0.5f / d3d->tout_w, 0.5f + 0.5f / d3d->tout_h, 0.0f);

	D3DXMatrixIdentity (&d3d->m_matWorld2);
}

static void settransform2 (struct d3dstruct *d3d, struct shaderdata *s)
{
	// Projection is (0,0,0) -> (1,1,1)
	MatrixOrthoOffCenterLH (&d3d->m_matPreProj, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
	// Align texels with pixels
	MatrixTranslation (&d3d->m_matPreView, -0.5f / d3d->window_w, 0.5f / d3d->window_h, 0.0f);
	// Identity for world
	D3DXMatrixIdentity (&d3d->m_matPreWorld);

	MatrixOrthoOffCenterLH (&d3d->m_matProj2, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);

	MatrixTranslation (&d3d->m_matView2, 0.5f - 0.5f / d3d->window_w, 0.5f + 0.5f / d3d->window_h, 0.0f);
	D3DXMatrixIdentity (&d3d->m_matWorld2);
}

static void freetextures (struct d3dstruct *d3d)
{
	d3d->usetexture = NULL;
	if (d3d->texture2) {
		d3d->texture2->Release();
		d3d->texture2 = NULL;
	}
	if (d3d->texture1) {
		d3d->texture1->Release();
		d3d->texture1 = NULL;
	}
	for (int i = 0; i < MAX_SHADERS; i++) {
		struct shaderdata *s = &d3d->shaders[i];
		if (s->lpTempTexture) {
			s->lpTempTexture->Release ();
			s->lpTempTexture = NULL;
		}
		if (s->lpWorkTexture1) {
			s->lpWorkTexture1->Release ();
			s->lpWorkTexture1 = NULL;
		}
		if (s->lpWorkTexture2) {
			s->lpWorkTexture2->Release ();
			s->lpWorkTexture2 = NULL;
		}
		if (s->lpHq2xLookupTexture) {
			s->lpHq2xLookupTexture->Release ();
			s->lpHq2xLookupTexture = NULL;
		}
	}
	if (d3d->lpPostTempTexture) {
		d3d->lpPostTempTexture->Release();
		d3d->lpPostTempTexture = NULL;
	}
}

static void getswapchain (struct d3dstruct *d3d)
{
	if (!d3d->d3dswapchain) {
		HRESULT hr = d3d->d3ddev->GetSwapChain (0, &d3d->d3dswapchain);
		if (FAILED (hr)) {
			write_log (_T("%s: GetSwapChain() failed, %s\n"), D3DHEAD, D3D_ErrorString (hr));
		}
	}
}

static void invalidatedeviceobjects (struct d3dstruct *d3d)
{
	if (d3d->filenotificationhandle != NULL)
		FindCloseChangeNotification  (d3d->filenotificationhandle);
	d3d->filenotificationhandle = NULL;
	freetextures (d3d);
	if (d3d->query) {
		d3d->query->Release();
		d3d->query = NULL;
	}
	if (d3d->sprite) {
		d3d->sprite->Release ();
		d3d->sprite = NULL;
	}
	if (d3d->ledtexture) {
		d3d->ledtexture->Release ();
		d3d->ledtexture = NULL;
	}
	if (d3d->sltexture) {
		d3d->sltexture->Release ();
		d3d->sltexture = NULL;
	}
	if (d3d->mask2texture) {
		d3d->mask2texture->Release ();
		d3d->mask2texture = NULL;
	}
	for (int i = 0; overlayleds[i]; i++) {
		if (d3d->mask2textureleds[i])
			d3d->mask2textureleds[i]->Release();
		d3d->mask2textureleds[i] = NULL;
	}
	if (d3d->mask2textureled_power_dim) {
		d3d->mask2textureled_power_dim->Release();
		d3d->mask2textureled_power_dim = NULL;
	}
	if (d3d->blanktexture) {
		d3d->blanktexture->Release ();
		d3d->blanktexture = NULL;
	}
	if (d3d->cursorsurfaced3d) {
		d3d->cursorsurfaced3d->Release ();
		d3d->cursorsurfaced3d = NULL;
	}
	xfree(d3d->cursorsurfaced3dtexbuf);
	d3d->cursorsurfaced3dtexbuf = NULL;
	struct d3d9overlay *ov = d3d->extoverlays;
	while (ov) {
		struct d3d9overlay *next = ov->next;
		if (ov->tex)
			ov->tex->Release();
		xfree(ov);
		ov = next;
	}
	d3d->extoverlays = NULL;
	for (int i = 0; i < MAX_SHADERS; i++) {
		if (d3d->shaders[i].pEffect) {
			d3d->shaders[i].pEffect->Release ();
			d3d->shaders[i].pEffect = NULL;
		}
		if (d3d->shaders[i].masktexture) {
			d3d->shaders[i].masktexture->Release ();
			d3d->shaders[i].masktexture = NULL;
		}
		memset (&d3d->shaders[i], 0, sizeof (struct shaderdata));
	}
	postEffect_freeParameters(d3d);
	if (d3d->d3ddev)
		d3d->d3ddev->SetStreamSource (0, NULL, 0, 0);
	if (d3d->vertexBuffer) {
		d3d->vertexBuffer->Release ();
		d3d->vertexBuffer = NULL;
	}
	if (d3d->d3dswapchain)  {
		d3d->d3dswapchain->Release ();
		d3d->d3dswapchain = NULL;
	}
	d3d->locked = 0;
	d3d->fulllocked = 0;
	d3d->fakelock = 0;
	d3d->maskshift.x = d3d->maskshift.y = d3d->maskshift.z = d3d->maskshift.w = 0;
	d3d->maskmult.x = d3d->maskmult.y = d3d->maskmult.z = d3d->maskmult.w = 0;
}

static struct shaderdata *allocshaderslot (struct d3dstruct *d3d, int type)
{
	for (int i = 0; i < MAX_SHADERS; i++) {
		if (d3d->shaders[i].type == 0) {
			d3d->shaders[i].type = type;
			return &d3d->shaders[i];
		}
	}
	return NULL;
}

static int restoredeviceobjects (struct d3dstruct *d3d)
{
	int vbsize;
	int wasshader = shaderon;
	HRESULT hr;

	invalidatedeviceobjects (d3d);
	getswapchain (d3d);

	while (shaderon > 0) {
		d3d->shaders[SHADER_POST].type = SHADERTYPE_POST;
		if (!psEffect_LoadEffect (d3d, d3d->psEnabled ? _T("_winuae.fx") : _T("_winuae_old.fx"), false, &d3d->shaders[SHADER_POST], -1)) {
			shaderon = 0;
			break;
		}
		for (int i = 0; i < MAX_FILTERSHADERS; i++) {
			if (d3d->filterd3d->gfx_filtershader[i][0]) {
				struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_BEFORE);
				if (!psEffect_LoadEffect (d3d, d3d->filterd3d->gfx_filtershader[i], true, s, i)) {
					d3d->filterd3d->gfx_filtershader[i][0] = changed_prefs.gf[d3d->filterd3didx].gfx_filtershader[i][0] = 0;
					break;
				}
			}
			if (d3d->filterd3d->gfx_filtermask[i][0]) {
				struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_MASK_BEFORE);
				createmasktexture (d3d, d3d->filterd3d->gfx_filtermask[i], s);
			}
		}
		if (d3d->filterd3d->gfx_filtershader[2 * MAX_FILTERSHADERS][0]) {
			struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_MIDDLE);
			if (!psEffect_LoadEffect (d3d, d3d->filterd3d->gfx_filtershader[2 * MAX_FILTERSHADERS], true, s, 2 * MAX_FILTERSHADERS)) {
				d3d->filterd3d->gfx_filtershader[2 * MAX_FILTERSHADERS][0] = changed_prefs.gf[d3d->filterd3didx].gfx_filtershader[2 * MAX_FILTERSHADERS][0] = 0;
			}
		}
		if (d3d->filterd3d->gfx_filtermask[2 * MAX_FILTERSHADERS][0]) {
			struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_MASK_MIDDLE);
			createmasktexture (d3d, d3d->filterd3d->gfx_filtermask[2 * MAX_FILTERSHADERS], s);
		}
		for (int i = 0; i < MAX_FILTERSHADERS; i++) {
			if (d3d->filterd3d->gfx_filtershader[i + MAX_FILTERSHADERS][0]) {
				struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_AFTER);
				if (!psEffect_LoadEffect (d3d, d3d->filterd3d->gfx_filtershader[i + MAX_FILTERSHADERS], true, s, i + MAX_FILTERSHADERS)) {
					d3d->filterd3d->gfx_filtershader[i + MAX_FILTERSHADERS][0] = changed_prefs.gf[d3d->filterd3didx].gfx_filtershader[i + MAX_FILTERSHADERS][0] = 0;
					break;
				}
			}
			if (d3d->filterd3d->gfx_filtermask[i + MAX_FILTERSHADERS][0]) {
				struct shaderdata *s = allocshaderslot (d3d, SHADERTYPE_MASK_AFTER);
				createmasktexture (d3d, d3d->filterd3d->gfx_filtermask[i + MAX_FILTERSHADERS], s);
			}
		}
		break;
	}
	if (d3d->filterd3d->gfx_filter_scanlines > 0) {
		createsltexture(d3d);
		createscanlines(d3d, 1);
	}
	if (wasshader && !shaderon)
		write_log (_T("Falling back to non-shader mode\n"));

	createmask2texture (d3d, d3d->filterd3d->gfx_filteroverlay);

	createledtexture (d3d);

	hr = D3DXCreateSprite (d3d->d3ddev, &d3d->sprite);
	if (FAILED (hr)) {
		write_log (_T("%s: D3DXSprite failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
	}

	int curw = CURSORMAXWIDTH, curh = CURSORMAXHEIGHT;
	d3d->cursorsurfaced3d = createtext (d3d, curw, curh, D3DFMT_A8R8G8B8);
	d3d->cursorsurfaced3dtexbuf = xcalloc(uae_u8, curw * curh * 4);
	d3d->cursorsurfaced3dtexbufupdated = false;
	d3d->cursor_v = false;
	d3d->cursor_scale = false;

	vbsize = sizeof (struct TLVERTEX) * NUMVERTICES;
	if (FAILED (hr = d3d->d3ddev->CreateVertexBuffer (vbsize, D3DUSAGE_WRITEONLY,
		D3DFVF_TLVERTEX, D3DPOOL_DEFAULT, &d3d->vertexBuffer, NULL))) {
			write_log (_T("%s: failed to create vertex buffer: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
	}
	createvertex (d3d);
	if (FAILED (hr = d3d->d3ddev->SetFVF (D3DFVF_TLVERTEX)))
		write_log (_T("%s: SetFVF failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
	if (FAILED (hr = d3d->d3ddev->SetStreamSource (0, d3d->vertexBuffer, 0, sizeof (struct TLVERTEX))))
		write_log (_T("%s: SetStreamSource failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));

	hr = d3d->d3ddev->SetRenderState (D3DRS_CULLMODE, D3DCULL_NONE);
	hr = d3d->d3ddev->SetRenderState (D3DRS_LIGHTING, FALSE);

	settransform (d3d, NULL);

	return 1;
}

static void D3D_free2 (struct d3dstruct *d3d)
{
	invalidatedeviceobjects (d3d);
	if (d3d->screenshotsurface)
		d3d->screenshotsurface->Release();
	d3d->screenshotsurface = NULL;
	if (d3d->d3ddev) {
		d3d->d3ddev->Release ();
		d3d->d3ddev = NULL;
	}
	if (d3d->d3d) {
		d3d->d3d->Release ();
		d3d->d3d = NULL;
	}
	d3d->d3d_enabled = 0;
	d3d->psActive = FALSE;
	d3d->resetcount = 0;
	d3d->devicelost = 0;
	d3d->renderdisabled = false;
	for (int i = 0; i < LED_MAX; i++) {
		leds[i] = 0;
	}
	changed_prefs.leds_on_screen &= ~STATUSLINE_TARGET;
	currprefs.leds_on_screen &= ~STATUSLINE_TARGET;
}

void xD3D_free (int monid, bool immediate)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	if (!fakemodewaitms || immediate) {
		waitfakemode (d3d);
		D3D_free2 (d3d);
		return;
	}
}

#define VBLANKDEBUG 0

static bool xD3D_getvblankpos (int *vpos)
{
	struct d3dstruct *d3d = &d3ddata[0];
	HRESULT hr;
	D3DRASTER_STATUS rt;
#if VBLANKDEBUG
	static UINT lastline;
	static BOOL lastinvblank;
#endif
	*vpos = -2;
	if (!isd3d (d3d))
		return false;
	if (d3d->d3dswapchain)
		hr = d3d->d3dswapchain->GetRasterStatus (&rt);
	else
		hr = d3d->d3ddev->GetRasterStatus (0, &rt);
	if (FAILED (hr)) {
		write_log (_T("%s: GetRasterStatus %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return false;
	}
	if (rt.ScanLine > d3d->maxscanline)
		d3d->maxscanline = rt.ScanLine;
	*vpos = rt.ScanLine;
#if VBLANKDEBUG
	if (lastline != rt.ScanLine || lastinvblank != rt.InVBlank) {
		write_log(_T("%d:%d "), rt.InVBlank ? 1 : 0, rt.ScanLine);
		lastline = rt.ScanLine;
		lastinvblank = rt.InVBlank;
	}
#endif
	if (rt.InVBlank != 0)
		*vpos = -1;
	return true;
}

static void xD3D_vblank_reset (double freq)
{
	struct d3dstruct *d3d = &d3ddata[0];
	if (!isd3d (d3d))
		return;
}

static int getd3dadapter (IDirect3D9 *id3d)
{
	struct MultiDisplay *md = getdisplay(&currprefs, 0);
	int num = id3d->GetAdapterCount ();
	HMONITOR winmon;
	POINT pt;

	pt.x = (md->rect.right - md->rect.left) / 2 + md->rect.left;
	pt.y = (md->rect.bottom - md->rect.top) / 2 + md->rect.top;
	winmon = MonitorFromPoint (pt, MONITOR_DEFAULTTONEAREST);
	for (int i = 0; i < num; i++) {
		HMONITOR d3dmon = id3d->GetAdapterMonitor (i);
		if (d3dmon == winmon)
			return i;
	}
	return D3DADAPTER_DEFAULT;
}

static const TCHAR *D3D_init2 (struct d3dstruct *d3d, HWND ahwnd, int w_w, int w_h, int depth, int *freq, int mmulth, int mmultv)
{
	int monid = d3d->num;
	struct amigadisplay *ad = &adisplays[monid];
	HRESULT ret, hr;
	static TCHAR errmsg[300] = { 0 };
	D3DDISPLAYMODE mode = { 0 };
	D3DCAPS9 d3dCaps;
	int adapter;
	DWORD flags;
	HINSTANCE d3dDLL, d3dx;
	typedef HRESULT (WINAPI *LPDIRECT3DCREATE9EX)(UINT, IDirect3D9Ex**);
	LPDIRECT3DCREATE9EX d3dexp = NULL;
	int vsync = isvsync ();
	struct apmode *apm = ad->picasso_on ? &currprefs.gfx_apmode[APMODE_RTG] : &currprefs.gfx_apmode[APMODE_NATIVE];
	struct apmode ap;
	D3DADAPTER_IDENTIFIER9 did;

	d3d->filterd3didx = ad->gf_index;
	d3d->filterd3d = &currprefs.gf[d3d->filterd3didx];

	D3D_free2 (d3d);
	if (!currprefs.gfx_api) {
		_tcscpy (errmsg, _T("D3D: not enabled"));
		return errmsg;
	}

	xfree (d3d->fakebitmap);
	d3d->fakebitmap = xmalloc (uae_u8, w_w * depth);

	d3dx = LoadLibrary (D3DX9DLL);
	if (d3dx == NULL) {
		static bool warned;
		if (!warned) {
			_tcscpy(errmsg, _T("Direct3D: Optional DirectX9 components are not installed.\n")
				_T("\nhttps://www.microsoft.com/en-us/download/details.aspx?id=8109"));
			warned = true;
		}
		return errmsg;
	}
	FreeLibrary (d3dx);

	D3D_goodenough ();
	D3D_canshaders ();

	d3d->d3d_ex = FALSE;
	d3dDLL = LoadLibrary (_T("D3D9.DLL"));
	if (d3dDLL == NULL) {
		_tcscpy (errmsg, _T("Direct3D: DirectX 9 or newer required"));
		return errmsg;
	} else {
		d3dexp  = (LPDIRECT3DCREATE9EX)GetProcAddress (d3dDLL, "Direct3DCreate9Ex");
		if (d3dexp)
			d3d->d3d_ex = TRUE;
	}
	FreeLibrary (d3dDLL);
	hr = -1;
	if (d3d->d3d_ex && D3DEX) {
		hr = d3dexp (D3D_SDK_VERSION, &d3d->d3dex);
		if (FAILED (hr))
			write_log (_T("Direct3D: failed to create D3DEx object: %s\n"), D3D_ErrorString (hr));
		d3d->d3d = (IDirect3D9*)d3d->d3dex;
	}
	if (FAILED (hr)) {
		d3d->d3d_ex = 0;
		d3d->d3dex = NULL;
		d3d->d3d = Direct3DCreate9 (D3D_SDK_VERSION);
		if (d3d->d3d == NULL) {
			D3D_free(monid, true);
			_tcscpy (errmsg, _T("Direct3D: failed to create D3D object"));
			return errmsg;
		}
	}
	if (d3d->d3d_ex)
		D3DHEAD = _T("D3D9Ex");
	else
		D3DHEAD = _T("D3D9");

	memcpy(&ap, apm, sizeof ap);

	if (os_dwm_enabled && isfullscreen() <= 0 && apm->gfx_backbuffers > 1 && !apm->gfx_vsync) {
		write_log(_T("Switch from triple buffer to double buffer (%d).\n"), apm->gfx_vflip);
		ap.gfx_vflip = 0;
		ap.gfx_backbuffers = 1;
	}

	adapter = getd3dadapter (d3d->d3d);

	d3d->modeex.Size = sizeof d3d->modeex;
	if (d3d->d3dex && D3DEX) {
		LUID luid;
		hr = d3d->d3dex->GetAdapterLUID (adapter, &luid);
		hr = d3d->d3dex->GetAdapterDisplayModeEx (adapter, &d3d->modeex, NULL);
	}
	if (FAILED (hr = d3d->d3d->GetAdapterDisplayMode (adapter, &mode)))
		write_log (_T("%s: GetAdapterDisplayMode failed %s\n"), D3DHEAD, D3D_ErrorString (hr));
	if (FAILED (hr = d3d->d3d->GetDeviceCaps (adapter, D3DDEVTYPE_HAL, &d3dCaps)))
		write_log (_T("%s: GetDeviceCaps failed %s\n"), D3DHEAD, D3D_ErrorString (hr));
	if (SUCCEEDED (hr = d3d->d3d->GetAdapterIdentifier (adapter, 0, &did))) {
		TCHAR *s = au (did.Description);
		write_log (_T("Device name: '%s' %llx.%x\n"), s, did.DriverVersion, did.Revision);
		xfree (s);
	}

	d3d->variablerefresh = false;
	cannoclear = ap.gfx_vsyncmode != 0;

	memset (&d3d->dpp, 0, sizeof (d3d->dpp));
	d3d->dpp.Windowed = isfullscreen () <= 0;
	d3d->dpp.BackBufferFormat = mode.Format;
	d3d->dpp.BackBufferCount = ap.gfx_backbuffers;
	d3d->dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3d->dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
	d3d->dpp.BackBufferWidth = w_w;
	d3d->dpp.BackBufferHeight = w_h;
	d3d->dpp.PresentationInterval = d3d->variablerefresh ? D3DPRESENT_INTERVAL_DEFAULT : ((!ap.gfx_vflip || monid) ? D3DPRESENT_INTERVAL_IMMEDIATE : D3DPRESENT_INTERVAL_ONE);

	d3d->modeex.Width = w_w;
	d3d->modeex.Height = w_h;
	d3d->modeex.RefreshRate = 0;
	d3d->modeex.ScanLineOrdering = ap.gfx_interlaced ? D3DSCANLINEORDERING_INTERLACED : D3DSCANLINEORDERING_PROGRESSIVE;
	d3d->modeex.Format = mode.Format;

	d3d->vsync2 = 0;
	int hzmult = 0;
	if (isfullscreen () > 0) {
		d3d->dpp.FullScreen_RefreshRateInHz = getrefreshrate(monid, d3d->modeex.Width, d3d->modeex.Height);
		d3d->modeex.RefreshRate = d3d->dpp.FullScreen_RefreshRateInHz;
		if (vsync > 0) {
			d3d->dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
			getvsyncrate(monid, (float)d3d->dpp.FullScreen_RefreshRateInHz, &hzmult);
			if (hzmult < 0) {
				if (!ap.gfx_strobo) {
					if (d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_TWO)
						d3d->dpp.PresentationInterval = D3DPRESENT_INTERVAL_TWO;
				}  else {
					d3d->vsync2 = -2;
				}
			} else if (hzmult > 0) {
				d3d->vsync2 = 1;
			}
		}
		*freq = d3d->modeex.RefreshRate;
	} else {
		if (mode.RefreshRate > 0) {
			if (vsync > 0) {
				d3d->dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
				getvsyncrate(monid, (float)mode.RefreshRate, &hzmult);
				if (hzmult < 0) {
					if (!ap.gfx_strobo) {
						if ((d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_TWO) && isfullscreen() > 0)
							d3d->dpp.PresentationInterval = D3DPRESENT_INTERVAL_TWO;
					} else {
						d3d->vsync2 = -2;
					}
				} else if (hzmult > 0) {
					d3d->vsync2 = 1;
				}
			}
			*freq = mode.RefreshRate;
		}
	}

	if (vsync < 0) {
		d3d->vsync2 = 0;
		getvsyncrate(monid, (float)(isfullscreen() > 0 ? d3d->dpp.FullScreen_RefreshRateInHz : mode.RefreshRate), &hzmult);
		if (hzmult > 0) {
			d3d->vsync2 = 1;
		} else if (hzmult < 0) {
			if (ap.gfx_strobo) {
				d3d->vsync2 = -2;
			} else if (ap.gfx_vflip) {
				if ((d3dCaps.PresentationIntervals & D3DPRESENT_INTERVAL_TWO) && isfullscreen() > 0)
					d3d->dpp.PresentationInterval = D3DPRESENT_INTERVAL_TWO;
				else
					d3d->vsync2 = -1;
			}
		}
	}

	d3d->d3dhwnd = ahwnd;
	d3d->t_depth = depth;

	flags = D3DCREATE_FPU_PRESERVE | D3DCREATE_MULTITHREADED;
	// Check if hardware vertex processing is available
	if(d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
		flags |= D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
	} else {
		flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}
	if (d3d->d3d_ex && D3DEX) {
		ret = d3d->d3dex->CreateDeviceEx (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, d3d->dpp.Windowed ? NULL : &d3d->modeex, &d3d->d3ddevex);
		d3d->d3ddev = d3d->d3ddevex;
	} else {
		ret = d3d->d3d->CreateDevice (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, &d3d->d3ddev);
	}
	if (FAILED (ret) && (flags & D3DCREATE_PUREDEVICE)) {
		flags &= ~D3DCREATE_PUREDEVICE;
		if (d3d->d3d_ex && D3DEX) {
			ret = d3d->d3dex->CreateDeviceEx (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, d3d->dpp.Windowed ? NULL : &d3d->modeex, &d3d->d3ddevex);
			d3d->d3ddev = d3d->d3ddevex;
		} else {
			ret = d3d->d3d->CreateDevice (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, &d3d->d3ddev);
		}
		if (FAILED (ret) && (flags & D3DCREATE_HARDWARE_VERTEXPROCESSING)) {
			flags &= ~D3DCREATE_HARDWARE_VERTEXPROCESSING;
			flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
			if (d3d->d3d_ex && D3DEX) {
				ret = d3d->d3dex->CreateDeviceEx (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, d3d->dpp.Windowed ? NULL : &d3d->modeex, &d3d->d3ddevex);
				d3d->d3ddev = d3d->d3ddevex;
			} else {
				ret = d3d->d3d->CreateDevice (adapter, D3DDEVTYPE_HAL, d3d->d3dhwnd, flags, &d3d->dpp, &d3d->d3ddev);
			}
		}
	}

	if (FAILED (ret)) {
		_stprintf (errmsg, _T("%s failed, %s\n"), d3d->d3d_ex && D3DEX ? _T("CreateDeviceEx") : _T("CreateDevice"), D3D_ErrorString (ret));
		if (d3d->d3d_ex && D3DEX) {
			write_log (_T("%s\n"), errmsg);
			D3DEX = 0;
			return D3D_init2(d3d, ahwnd, w_w, w_h, depth, freq, mmulth, mmultv);
		}
		D3D_free(monid, true);
		return errmsg;
	}

	if (d3dCaps.PixelShaderVersion >= D3DPS_VERSION(2, 0))
		d3d->psEnabled = TRUE;
	else
		d3d->psEnabled = FALSE;

	d3d->max_texture_w = d3dCaps.MaxTextureWidth;
	d3d->max_texture_h = d3dCaps.MaxTextureHeight;

	write_log (_T("%s: %08X %08X %08X %08X"), D3DHEAD, flags, d3dCaps.Caps, d3dCaps.Caps2, d3dCaps.Caps3);
	if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY)
		write_log (_T(" SQUAREONLY"));
	if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_POW2)
		write_log (_T(" POW2"));
	if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL)
		write_log (_T(" NPOTCONDITIONAL"));
	if (d3dCaps.TextureCaps & D3DPTEXTURECAPS_ALPHA)
		write_log (_T(" ALPHA"));
	if (d3dCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES)
		write_log (_T(" DYNAMIC"));
	if (d3dCaps.Caps & D3DCAPS_READ_SCANLINE)
		write_log (_T(" SCANLINE"));
	
	write_log (_T("\n"));

	write_log (_T("%s: PS=%d.%d VS=%d.%d %d*%d*%d%s%s VS=%d B=%d%s %d-bit %d (%dx%d)\n"),
		D3DHEAD,
		(d3dCaps.PixelShaderVersion >> 8) & 0xff, d3dCaps.PixelShaderVersion & 0xff,
		(d3dCaps.VertexShaderVersion >> 8) & 0xff, d3dCaps.VertexShaderVersion & 0xff,
		d3d->modeex.Width, d3d->modeex.Height,
		d3d->dpp.FullScreen_RefreshRateInHz,
		ap.gfx_interlaced ? _T("i") : _T("p"),
		d3d->dpp.Windowed ? _T("") : _T(" FS"),
		vsync, ap.gfx_backbuffers,
		ap.gfx_vflip < 0 ? _T("WE") : (ap.gfx_vflip > 0 ? _T("WS") :  _T("I")), 
		d3d->t_depth, adapter,
		d3d->max_texture_w, d3d->max_texture_h
	);

#if 0
	if ((d3dCaps.PixelShaderVersion < D3DPS_VERSION(2,0) || !d3d->psEnabled || d3d->max_texture_w < 2048 || d3d->max_texture_h < 2048 || (!shaderon && SHADER > 0)) && d3d->d3d_ex) {
		D3DEX = 0;
		write_log (_T("Disabling D3D9Ex\n"));
		if (d3d->d3ddev) {
			d3d->d3ddev->Release ();
			d3d->d3ddev = NULL;
		}
		if (d3d->d3d) {
			d3d->d3d->Release ();
			d3d->d3d = NULL;
		}
		d3d->d3ddevex = NULL;
		return D3D_init (ahwnd, w_w, w_h, depth, freq, mmult);
	}
#endif

	if (!shaderon)
		write_log (_T("Using non-shader version\n"));

	d3d->dmultxh = mmulth;
	d3d->dmultxv = mmultv;
	d3d->dmult = S2X_getmult(d3d->num);

	d3d->window_w = w_w;
	d3d->window_h = w_h;

	if (d3d->max_texture_w < w_w  || d3d->max_texture_h < w_h) {
		_stprintf (errmsg, _T("%s: %d * %d or bigger texture support required\nYour card's maximum texture size is only %d * %d"),
			D3DHEAD, w_w, w_h, d3d->max_texture_w, d3d->max_texture_h);
		return errmsg;
	}
	while (d3d->dmultxh > 1 && w_w * d3d->dmultxh > d3d->max_texture_w)
		d3d->dmultxh--;
	while (d3d->dmultxv > 1 && w_h * d3d->dmultxv > d3d->max_texture_h)
		d3d->dmultxv--;

	d3d->required_sl_texture_w = w_w;
	d3d->required_sl_texture_h = w_h;
	if (d3d->filterd3d->gfx_filter_scanlines > 0 && (d3d->max_texture_w < w_w || d3d->max_texture_h < w_h)) {
		gui_message (_T("%s: %d * %d or bigger texture support required for scanlines (max is only %d * %d)\n"),
			D3DHEAD, _T("Scanlines disabled."),
			d3d->required_sl_texture_w, d3d->required_sl_texture_h, d3d->max_texture_w, d3d->max_texture_h);
		changed_prefs.gf[d3d->filterd3didx].gfx_filter_scanlines = d3d->filterd3d->gfx_filter_scanlines = 0;
	}

	switch (depth)
	{
		case 32:
		default:
			d3d->tformat = D3DFMT_X8R8G8B8;
		break;
		case 15:
			d3d->tformat = D3DFMT_X1R5G5B5;
		break;
		case 16:
			d3d->tformat = D3DFMT_R5G6B5;
		break;
	}

	changed_prefs.leds_on_screen |= STATUSLINE_TARGET;
	currprefs.leds_on_screen |= STATUSLINE_TARGET;

	if (!restoredeviceobjects (d3d)) {
		D3D_free(monid, true);
		_stprintf (errmsg, _T("%s: initialization failed."), D3DHEAD);
		return errmsg;
	}
	d3d->maxscanline = 0;
	d3d->d3d_enabled = 1;
	d3d->wasstilldrawing_broken = true;

	if ((vsync < 0 || d3d->variablerefresh) && ap.gfx_vflip == 0) {
		hr = d3d->d3ddev->CreateQuery(D3DQUERYTYPE_EVENT, &d3d->query);
		if (FAILED (hr))
			write_log (_T("%s: CreateQuery(D3DQUERYTYPE_EVENT) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
	}
	if (d3d->d3ddevex) {
		UINT v = 12345;
		hr = d3d->d3ddevex->GetMaximumFrameLatency (&v);
		if (FAILED (hr)) {
			write_log (_T("%s: GetMaximumFrameLatency() failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			v = 1;
		}
		hr = S_OK;
		if (forcedframelatency >= 0) {
			hr = d3d->d3ddevex->SetMaximumFrameLatency(forcedframelatency);
		} else if (ap.gfx_vsyncmode) {
			hr = d3d->d3ddevex->SetMaximumFrameLatency(1);
		} else if (d3d->dpp.PresentationInterval == D3DPRESENT_INTERVAL_IMMEDIATE && (v > 1 || !vsync)) {
			hr = d3d->d3ddevex->SetMaximumFrameLatency((vsync || d3d->variablerefresh) ? (hzmult < 0 && !ap.gfx_strobo && !d3d->variablerefresh ? 2 : 1) : 0);
		}
		if (FAILED (hr))
			write_log (_T("%s: SetMaximumFrameLatency() failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
	}

	hr = d3d->d3ddev->CreateOffscreenPlainSurface(w_w, w_h, d3d->tformat, D3DPOOL_SYSTEMMEM, &d3d->screenshotsurface, NULL);
	if (FAILED(hr)) {
		write_log(_T("%s: CreateOffscreenPlainSurface RT failed: %s\n"), D3DHEAD, D3D_ErrorString(hr));
	}

	return 0;
}

struct d3d_initargs
{
	HWND hwnd;
	int w;
	int h;
	int depth;
	int mmulth, mmultv;
	int *freq;
};
static struct d3d_initargs d3dargs;

static void D3D_init_start (void *p)
{
	struct d3dstruct *d3d = &d3ddata[0];
	struct timeval tv1, tv2;

	gettimeofday (&tv1, NULL);
	sleep_millis (1000);
	write_log (_T("Threaded D3D_init() start (free)\n"));
	D3D_free2 (d3d);
	sleep_millis (1000);
	write_log (_T("Threaded D3D_init() start (init)\n"));
	const TCHAR *t = D3D_init2 (d3d, d3dargs.hwnd, d3dargs.w, d3dargs.h,d3dargs.depth, d3dargs.freq, d3dargs.mmulth, d3dargs.mmultv);
	if (t) {
		gui_message (_T("Threaded D3D_init() returned error '%s'\n"), t);
	}
	write_log (_T("Threaded D3D_init() returned\n"));
	for (;;) {
		uae_u64 us1, us2, diff;
		gettimeofday (&tv2, NULL);
		us1 = (uae_u64)tv1.tv_sec * 1000000 + tv1.tv_usec;
		us2 = (uae_u64)tv2.tv_sec * 1000000 + tv2.tv_usec;
		diff = us2 - us1;
		if (diff >= fakemodewaitms * 1000)
			break;
		sleep_millis (10);
	}
	write_log (_T("Threaded D3D_init() finished\n"));
	d3d->frames_since_init = 0;
	d3d->fakemode = false;
}

static const TCHAR *xD3D_init (HWND ahwnd, int monid, int w_w, int w_h, int depth, int *freq, int mmulth, int mmultv, int *errp)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (!fakemodewaitms) {
		const TCHAR *v = D3D_init2(d3d, ahwnd, w_w, w_h, depth, freq, mmulth, mmultv);
		if (v != NULL) {
			*errp = 1;
		}
		return v;
	}
	d3d->fakemode = true;
	d3dargs.hwnd = ahwnd;
	d3dargs.w = w_w;
	d3dargs.h = w_h;
	d3dargs.depth = depth;
	d3dargs.mmulth = mmulth;
	d3dargs.mmultv = mmultv;
	d3dargs.freq = freq;
	uae_start_thread_fast (D3D_init_start, NULL, &d3d->fakemodetid);
	return NULL;
}

static bool alloctextures (struct d3dstruct *d3d)
{
	if (!createtexture (d3d, d3d->tout_w, d3d->tout_h, d3d->window_w, d3d->window_h))
		return false;
	if (!createamigatexture (d3d, d3d->tin_w, d3d->tin_h))
		return false;
	return true;
}

static bool xD3D_alloctexture (int monid, int w, int h)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (w < 0 || h < 0) {
		if (d3d->tino_w == -w && d3d->tino_h == -h && d3d->texture1) {
			return true;
		}
		return false;
	}

	d3d->tino_w = w;
	d3d->tino_h = h;
	d3d->tin_w = w * d3d->dmult;
	d3d->tin_h = h * d3d->dmult;

	d3d->tout_w = d3d->tin_w * d3d->dmultxh;
	d3d->tout_h = d3d->tin_h * d3d->dmultxv;

	if (d3d->fakemode)
		return false;

	setupscenecoords(d3d, true, monid);

	changed_prefs.leds_on_screen |= STATUSLINE_TARGET;
	currprefs.leds_on_screen |= STATUSLINE_TARGET;

	freetextures (d3d);
	return alloctextures (d3d);
}


static HRESULT reset (void)
{
	struct d3dstruct *d3d = &d3ddata[0];
	HRESULT hr;
	bool oldrender = d3d->renderdisabled;
	d3d->renderdisabled = true;
	if (d3d->d3dex)
		hr = d3d->d3ddevex->ResetEx (&d3d->dpp, d3d->dpp.Windowed ? NULL : &d3d->modeex);
	else
		hr = d3d->d3ddev->Reset (&d3d->dpp);
	d3d->renderdisabled = oldrender;
	return hr;
}

static int D3D_needreset (struct d3dstruct *d3d)
{
	HRESULT hr;

	if (!d3d->devicelost)
		return -1;
	if (d3d->d3dex)
		hr = d3d->d3ddevex->CheckDeviceState (d3d->d3dhwnd);
	else
		hr = d3d->d3ddev->TestCooperativeLevel ();
	if (hr == S_PRESENT_OCCLUDED) {
		// no need to draw anything
		return 1;
	}
	if (hr == D3DERR_DEVICELOST) {
		d3d->renderdisabled = true;
		// lost but can't be reset yet
		return 1;
	}
	if (hr == D3DERR_DEVICENOTRESET) {
		// lost and can be reset
		write_log (_T("%s: DEVICENOTRESET\n"), D3DHEAD);
		d3d->devicelost = 2;
		invalidatedeviceobjects (d3d);
		freetextures (d3d);
		hr = reset ();
		if (FAILED (hr)) {
			write_log (_T("%s: Reset failed %s\n"), D3DHEAD, D3D_ErrorString (hr));
			d3d->resetcount++;
			if (d3d->resetcount > 2 || hr == D3DERR_DEVICEHUNG) {
				changed_prefs.gfx_api = 0;
				write_log (_T("%s: Too many failed resets, disabling Direct3D mode\n"), D3DHEAD);
			}
			return 1;
		}
		d3d->devicelost = 0;
		write_log (_T("%s: Reset succeeded\n"), D3DHEAD);
		d3d->renderdisabled = false;
		restoredeviceobjects (d3d);
		alloctextures (d3d);
		return -1;
	} else if (hr == S_PRESENT_MODE_CHANGED) {
		write_log (_T("%s: S_PRESENT_MODE_CHANGED\n"), D3DHEAD);
	}
	if (SUCCEEDED (hr)) {
		d3d->devicelost = 0;
		invalidatedeviceobjects (d3d);
		hr = reset ();
		if (FAILED (hr))
			write_log (_T("%s: Reset failed %s\n"), D3DHEAD, D3D_ErrorString (hr));
		restoredeviceobjects (d3d);
		return -1;
	}
	write_log (_T("%s: TestCooperativeLevel %s\n"), D3DHEAD, D3D_ErrorString (hr));
	return 0;
}

static void D3D_showframe2 (struct d3dstruct *d3d, bool dowait)
{
	HRESULT hr;

	if (!isd3d (d3d))
		return;
	for (;;) {
		if (d3d->d3dswapchain)
			hr = d3d->d3dswapchain->Present (NULL, NULL, NULL, NULL, dowait ? 0 : D3DPRESENT_DONOTWAIT);
		else
			hr = d3d->d3ddev->Present (NULL, NULL, NULL, NULL);
		if (hr == D3DERR_WASSTILLDRAWING) {
			d3d->wasstilldrawing_broken = false;
			if (!dowait)
				return;
			sleep_millis (1);
			continue;
		} else if (hr == S_PRESENT_OCCLUDED) {
			d3d->renderdisabled = true;
		} else if (hr == S_PRESENT_MODE_CHANGED) {
			// In most cases mode actually didn't change but
			// D3D is just being stupid and not accepting
			// all modes that DirectDraw does accept,
			// for example interlaced or EDS_RAWMODE modes!
			//devicelost = 1;
			; //write_log (_T("S_PRESENT_MODE_CHANGED\n"));
		} else if (FAILED (hr)) {
			write_log (_T("%s: Present() %s\n"), D3DHEAD, D3D_ErrorString (hr));
			if (hr == D3DERR_DEVICELOST || hr == S_PRESENT_MODE_CHANGED) {
				d3d->devicelost = 1;
				d3d->renderdisabled = true;
				write_log (_T("%s: mode changed or fullscreen focus lost\n"), D3DHEAD);
			}
		}
		return;
	}
}

static void xD3D_restore(int monid, bool checkonly)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	if (checkonly)
		return;
	d3d->renderdisabled = false;
}

static void xD3D_clear (int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	int i;
	HRESULT hr;

	if (!isd3d (d3d))
		return;
	for (i = 0; i < 2; i++) {
		hr = d3d->d3ddev->Clear (0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, d3ddebug ? 0x80 : 0x00), 0, 0);
		D3D_showframe2 (d3d, true);		
	}
}

static LPDIRECT3DTEXTURE9 processshader(struct d3dstruct *d3d, LPDIRECT3DTEXTURE9 srctex, struct shaderdata *s, bool rendertarget)
{
	HRESULT hr;
	UINT uPasses, uPass;
	LPDIRECT3DSURFACE9 lpRenderTarget;
	LPDIRECT3DSURFACE9 lpNewRenderTarget;
	LPDIRECT3DTEXTURE9 lpWorkTexture;

	if (!psEffect_SetTextures (srctex, s))
		return NULL;
	if (s->psPreProcess) {
		if (!psEffect_SetMatrices (&d3d->m_matPreProj, &d3d->m_matPreView, &d3d->m_matPreWorld, s))
			return NULL;

		if (FAILED (hr = d3d->d3ddev->GetRenderTarget (0, &lpRenderTarget)))
			write_log (_T("%s: GetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		lpWorkTexture = s->lpWorkTexture1;
		lpNewRenderTarget = NULL;
pass2:
		if (FAILED (hr = lpWorkTexture->GetSurfaceLevel (0, &lpNewRenderTarget)))
			write_log (_T("%s: GetSurfaceLevel: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpNewRenderTarget)))
			write_log (_T("%s: SetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));

		uPasses = 0;
		if (psEffect_Begin ((lpWorkTexture == s->lpWorkTexture1) ? psEffect_PreProcess1 : psEffect_PreProcess2, &uPasses, s)) {
			for (uPass = 0; uPass < uPasses; uPass++) {
				if (psEffect_BeginPass (s->pEffect, uPass)) {
					if (FAILED (hr = d3d->d3ddev->DrawPrimitive (D3DPT_TRIANGLESTRIP, 4, 2))) {
						write_log (_T("%s: Effect DrawPrimitive failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
					}
					psEffect_EndPass (s->pEffect);
				}
			}
			psEffect_End (s->pEffect);
		}
		if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpRenderTarget)))
			write_log (_T("%s: Effect RenderTarget reset failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		lpNewRenderTarget->Release ();
		lpNewRenderTarget = NULL;
		if (psEffect_hasPreProcess2 (s) && lpWorkTexture == s->lpWorkTexture1) {
			lpWorkTexture = s->lpWorkTexture2;
			goto pass2;
		}
		lpRenderTarget->Release ();
		lpRenderTarget = NULL;
	}
	psEffect_SetMatrices (&d3d->m_matProj2, &d3d->m_matView2, &d3d->m_matWorld2, s);

	if (rendertarget) {
#if TWOPASS
		if (FAILED (hr = d3d->d3ddev->GetRenderTarget (0, &lpRenderTarget)))
			write_log (_T("%s: GetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		if (FAILED (hr = s->lpTempTexture->GetSurfaceLevel (0, &lpNewRenderTarget)))
			write_log (_T("%s: GetSurfaceLevel: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpNewRenderTarget)))
			write_log (_T("%s: SetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
#endif
	}

	uPasses = 0;
	if (psEffect_Begin (psEffect_Combine, &uPasses, s)) {
		for (uPass = 0; uPass < uPasses; uPass++) {
			if (!psEffect_BeginPass (s->pEffect, uPass))
				return NULL;
			if (FAILED (hr = d3d->d3ddev->DrawPrimitive (D3DPT_TRIANGLESTRIP, 0, 2)))
				write_log (_T("%s: Effect2 DrawPrimitive failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			psEffect_EndPass (s->pEffect);
		}
		psEffect_End (s->pEffect);
	}
	if (rendertarget) {
#if TWOPASS
		if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpRenderTarget)))
			write_log (_T("%s: SetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		lpNewRenderTarget->Release ();
		lpRenderTarget->Release ();
#endif
	}
	return s->lpTempTexture;
}

static void xD3D_led(int led, int on, int brightness)
{
	struct d3dstruct *d3d = &d3ddata[0];
	leds[led] = on;
}

static int xD3D_debug(int monid, int mode)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	int old = debugcolors ? 1 : 0;
	debugcolors = (mode & 1) != 0;
	noclear = debugcolors ? false : true;
	clearcnt = 0;
	return old;
}

static void clearrt(struct d3dstruct *d3d)
{
	HRESULT hr;
	uae_u8 color[4] = { 0, 0, 0, 0 };

	if (noclear && cannoclear) {
		if (clearcnt > 3)
			return;
		clearcnt++;
	}

	if (!noclear && debugcolors && slicecnt > 0) {
		int cnt = slicecnt - 1;
		int v = cnt % 3;
		if (cnt / 3 == 1)
			color[(v + 1) % 3] = 80;
		color[v] = 80;
	}

	hr = d3d->d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(color[0], d3ddebug ? 0x80 : color[1], color[2]), 0, 0);
}

static void D3D_render2(struct d3dstruct *d3d, int mode, int monid)
{
	struct AmigaMonitor *mon = &AMonitors[d3d - d3ddata];
	HRESULT hr;
	LPDIRECT3DTEXTURE9 srctex = d3d->usetexture;
	UINT uPasses, uPass;

	if (!isd3d (d3d) || !srctex)
		return;

	bool normalrender = mode < 0 || (mode & 1);

	if (mode > 0 && (mode & 2))
		slicecnt = 0;
	else if (mode < 0)
		slicecnt = slicecnt == 2 ? 0 : slicecnt;

	clearrt(d3d);

	slicecnt++;

	if (FAILED (hr = d3d->d3ddev->BeginScene ())) {
		write_log (_T("%s: BeginScene: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return;
	}
	if (shaderon > 0 && d3d->shaders[SHADER_POST].pEffect) {
		for (int i = 0; i < MAX_SHADERS; i++) {
			struct shaderdata *s = &d3d->shaders[i];
			if (s->type == SHADERTYPE_BEFORE)
				settransform_pre (d3d, s);
			if (s->type == SHADERTYPE_MIDDLE) {
				d3d->m_matProj = d3d->m_matProj_out;
				d3d->m_matView = d3d->m_matView_out;
				d3d->m_matWorld = d3d->m_matWorld_out;
			}
			if (s->type == SHADERTYPE_BEFORE || s->type == SHADERTYPE_MIDDLE) {
				settransform (d3d, s);
				srctex = processshader (d3d, srctex, s, true);
				if (!srctex)
					return;
			}
		}
	}

	d3d->m_matProj = d3d->m_matProj_out;
	d3d->m_matView = d3d->m_matView_out;
	d3d->m_matWorld = d3d->m_matWorld_out;

#if TWOPASS
	if (shaderon > 0 && d3d->shaders[SHADER_POST].pEffect) {
		LPDIRECT3DSURFACE9 lpRenderTarget;
		LPDIRECT3DSURFACE9 lpNewRenderTarget;
		struct shaderdata *s = &d3d->shaders[SHADER_POST];
		LPD3DXEFFECT postEffect = s->pEffect;
		int after = -1;
		LPDIRECT3DTEXTURE9 masktexture = NULL;
		D3DSURFACE_DESC Desc;
		D3DXVECTOR4 texelsize;

		for (int i = 0; i < MAX_SHADERS; i++) {
			struct shaderdata *s = &d3d->shaders[i];
			if (s->type == SHADERTYPE_AFTER)
				after = i;
			if (s->type == SHADERTYPE_MASK_MIDDLE && s->masktexture)
				masktexture = s->masktexture;
		}

		setupscenecoords(d3d, normalrender, monid);
		hr = d3d->d3ddev->SetTransform (D3DTS_PROJECTION, &d3d->m_matProj);
		hr = d3d->d3ddev->SetTransform (D3DTS_VIEW, &d3d->m_matView);
		hr = d3d->d3ddev->SetTransform (D3DTS_WORLD, &d3d->m_matWorld);

		hr = postEffect->SetMatrix (d3d->postMatrixSource, &d3d->postproj);
		hr = postEffect->SetVector (d3d->postMaskMult, &d3d->maskmult);
		hr = postEffect->SetVector (d3d->postMaskShift, &d3d->maskshift);

		srctex->GetLevelDesc (0, &Desc);
		texelsize.x = 1.0f / Desc.Width;
		texelsize.y = 1.0f / Desc.Height;
		texelsize.z = 1; texelsize.w = 1;
		hr = postEffect->SetVector (d3d->postTexelSize, &texelsize);
		if (d3d->postFramecounterHandle)
			postEffect->SetFloat(d3d->postFramecounterHandle, (FLOAT)vsync_counter);

		if (masktexture) {
			if (FAILED (hr = postEffect->SetTechnique (d3d->postTechnique)))
				write_log (_T("%s: SetTechnique(postTechnique) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			if (FAILED (hr = postEffect->SetTexture (d3d->postMaskTextureHandle, masktexture)))
				write_log (_T("%s: SetTexture(masktexture) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		} else if (d3d->sltexture) {
			if (FAILED (hr = postEffect->SetTechnique (d3d->postTechniqueAlpha)))
				write_log (_T("%s: SetTechnique(postTechniqueAlpha) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			if (FAILED (hr = postEffect->SetTexture (d3d->postMaskTextureHandle, d3d->sltexture)))
				write_log (_T("%s: SetTexture(sltexture) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		} else {
			if (FAILED (hr = postEffect->SetTechnique (d3d->postTechniquePlain)))
				write_log (_T("%s: SetTechnique(postTechniquePlain) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		}
		hr = postEffect->SetInt (d3d->postFilterMode, d3d->filterd3d->gfx_filter_bilinear ? D3DTEXF_LINEAR : D3DTEXF_POINT);

		if (FAILED (hr = postEffect->SetTexture (d3d->postSourceTextureHandle, srctex)))
			write_log (_T("%s: SetTexture(srctex) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));

		if (s->m_SourceDimsEffectHandle) {
			D3DXVECTOR4 fDimsSource;
			fDimsSource.x = (FLOAT)Desc.Width;
			fDimsSource.y = (FLOAT)Desc.Height;
			fDimsSource.z  = 1; fDimsSource.w = 1;
			hr = postEffect->SetVector(s->m_SourceDimsEffectHandle, &fDimsSource);
			if (FAILED(hr)) {
				write_log(_T("%s: SetTextures:SetVector:Source %s\n"), D3DHEAD, D3D_ErrorString(hr));
			}
		}
		if (s->m_TargetDimsEffectHandle) {
			D3DXVECTOR4 fDimsTarget;
			fDimsTarget.x = (FLOAT)s->targettex_width;
			fDimsTarget.y = (FLOAT)s->targettex_height;
			fDimsTarget.z = 1; fDimsTarget.w = 1;
			hr = postEffect->SetVector(s->m_TargetDimsEffectHandle, &fDimsTarget);
			if (FAILED(hr)) {
				write_log(_T("%s: SetTextures:SetVector:Target %s\n"), D3DHEAD, D3D_ErrorString(hr));
			}
		}

		if (after >= 0) {
			if (FAILED (hr = d3d->d3ddev->GetRenderTarget (0, &lpRenderTarget)))
				write_log (_T("%s: GetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			if (FAILED (hr = d3d->lpPostTempTexture->GetSurfaceLevel (0, &lpNewRenderTarget)))
				write_log (_T("%s: GetSurfaceLevel: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpNewRenderTarget)))
				write_log (_T("%s: SetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		}

		uPasses = 0;
		if (psEffect_Begin (psEffect_None, &uPasses, s)) {
			for (uPass = 0; uPass < uPasses; uPass++) {
				if (psEffect_BeginPass (postEffect, uPass)) {
					if (FAILED (hr = d3d->d3ddev->DrawPrimitive (D3DPT_TRIANGLESTRIP, 0, 2)))
						write_log (_T("%s: Post DrawPrimitive failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
					psEffect_EndPass (postEffect);
				}
			}
			psEffect_End (postEffect);
		}

		if (after >= 0) {
			if (FAILED (hr = d3d->d3ddev->SetRenderTarget (0, lpRenderTarget)))
				write_log (_T("%s: SetRenderTarget: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			lpNewRenderTarget->Release ();
			lpRenderTarget->Release ();

			srctex = d3d->lpPostTempTexture;
			for (int i = 0; i < MAX_SHADERS; i++) {
				struct shaderdata *s = &d3d->shaders[i];
				if (s->type == SHADERTYPE_AFTER) {
					settransform2 (d3d, s);
					srctex = processshader (d3d, srctex, s, i != after);
					if (!srctex)
						return;
				}
			}
		}

	} else {

		// non-shader version
		setupscenecoords (d3d, normalrender, monid);
		hr = d3d->d3ddev->SetTransform (D3DTS_PROJECTION, &d3d->m_matProj);
		hr = d3d->d3ddev->SetTransform (D3DTS_VIEW, &d3d->m_matView);
		hr = d3d->d3ddev->SetTransform (D3DTS_WORLD, &d3d->m_matWorld);
		hr = d3d->d3ddev->SetTexture (0, srctex);
		hr = d3d->d3ddev->DrawPrimitive (D3DPT_TRIANGLESTRIP, 0, 2);
		int bl = d3d->filterd3d->gfx_filter_bilinear ? D3DTEXF_LINEAR : D3DTEXF_POINT;
		hr = d3d->d3ddev->SetSamplerState(0, D3DSAMP_MINFILTER, bl);
		hr = d3d->d3ddev->SetSamplerState(0, D3DSAMP_MAGFILTER, bl);
		hr = d3d->d3ddev->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
		hr = d3d->d3ddev->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

		if (d3d->sprite && d3d->sltexture) {
			D3DXVECTOR3 v;
			d3d->sprite->Begin(D3DXSPRITE_ALPHABLEND);
			v.x = v.y = v.z = 0;
			d3d->sprite->Draw(d3d->sltexture, NULL, NULL, &v, 0xffffffff);
			d3d->sprite->End();
		}
	}

	if (d3d->sprite && ((d3d->ledtexture) || (d3d->mask2texture) || (d3d->cursorsurfaced3d && d3d->cursor_v))) {
		D3DXVECTOR3 v;
		d3d->sprite->Begin(D3DXSPRITE_ALPHABLEND);
		if (d3d->cursorsurfaced3d && d3d->cursor_v) {
			D3DXMATRIXA16 t;
			int bl = d3d->filterd3d->gfx_filter_bilinear ? D3DTEXF_LINEAR : D3DTEXF_POINT;
			d3d->d3ddev->SetSamplerState(0, D3DSAMP_MAGFILTER, bl);
			d3d->d3ddev->SetSamplerState(0, D3DSAMP_MINFILTER, bl);
			d3d->d3ddev->SetSamplerState(0, D3DSAMP_MIPFILTER, bl);
			if (d3d->cursor_scale)
				MatrixScaling(&t, ((float)(d3d->window_w) / (d3d->tout_w + 2 * d3d->cursor_offset2_x)) * d3d->cursor_mx, ((float)(d3d->window_h) / (d3d->tout_h + 2 * d3d->cursor_offset2_y)) * d3d->cursor_my, 0);
			else
				MatrixScaling(&t, d3d->cursor_mx, d3d->cursor_my, 0);
			v.x = (float)d3d->cursor_x + d3d->cursor_offset2_x;
			v.y = (float)d3d->cursor_y + d3d->cursor_offset2_y;
			v.z = 0.0f;
			d3d->sprite->SetTransform(&t);
			d3d->sprite->Draw(d3d->cursorsurfaced3d, NULL, NULL, &v, 0xffffffff);
			MatrixScaling(&t, 1, 1, 0);
			d3d->sprite->Flush();
			d3d->sprite->SetTransform(&t);
		}
		if (d3d->mask2texture) {
			D3DXMATRIXA16 t;
			RECT r;
			float srcw = d3d->mask2texture_w;
			float srch = d3d->mask2texture_h;
			float aspectsrc = srcw / srch;
			float aspectdst = (float)d3d->window_w / d3d->window_h;
			float w, h;

			w = d3d->mask2texture_multx;
			h = d3d->mask2texture_multy;
#if 0
			if (currprefs.gfx_filteroverlay_pos.width > 0)
				w = (float)currprefs.gfx_filteroverlay_pos.width / srcw;
			else if (currprefs.gfx_filteroverlay_pos.width == -1)
				w = 1.0;
			else if (currprefs.gfx_filteroverlay_pos.width <= -24000)
				w = w * (-currprefs.gfx_filteroverlay_pos.width - 30000) / 100.0;

			if (currprefs.gfx_filteroverlay_pos.height > 0)
				h = (float)currprefs.gfx_filteroverlay_pos.height / srch;
			else if (currprefs.gfx_filteroverlay_pos.height == -1)
				h = 1;
			else if (currprefs.gfx_filteroverlay_pos.height <= -24000)
				h = h * (-currprefs.gfx_filteroverlay_pos.height - 30000) / 100.0;
#endif
			MatrixScaling (&t, w, h, 0);

			v.x = 0;
			if (d3d->filterd3d->gfx_filteroverlay_pos.x == -1)
				v.x = (d3d->window_w - (d3d->mask2texture_w * w)) / 2.0f;
			else if (d3d->filterd3d->gfx_filteroverlay_pos.x > -24000)
				v.x = (float)d3d->filterd3d->gfx_filteroverlay_pos.x;
			else
				v.x = (d3d->window_w - (d3d->mask2texture_w * w)) / 2 + (-d3d->filterd3d->gfx_filteroverlay_pos.x - 30100) * d3d->window_w / 100.0f;

			v.y = 0;
			if (d3d->filterd3d->gfx_filteroverlay_pos.y == -1)
				v.y = (d3d->window_h - (d3d->mask2texture_h * h)) / 2.0f;
			else if (d3d->filterd3d->gfx_filteroverlay_pos.y > -24000)
				v.y = (float)d3d->filterd3d->gfx_filteroverlay_pos.y;
			else
				v.y = (d3d->window_h - (d3d->mask2texture_h * h)) / 2 + (-d3d->filterd3d->gfx_filteroverlay_pos.y - 30100) * d3d->window_h / 100.0f;

			v.x /= w;
			v.y /= h;
			v.x = v.y = 0;
			v.z = 0;
			v.x += d3d->mask2texture_offsetw / w;

			r.left = 0;
			r.top = 0;
			r.right = (LONG)d3d->mask2texture_w;
			r.bottom = (LONG)d3d->mask2texture_h;
			if (showoverlay) {
				d3d->sprite->SetTransform(&t);
				d3d->sprite->Draw(d3d->mask2texture, &r, NULL, &v, 0xffffffff);
				d3d->sprite->Flush();
				for (int i = 0; overlayleds[i]; i++) {
					bool led = leds[ledtypes[i]] != 0;
					if (led || (ledtypes[i] == LED_POWER && currprefs.power_led_dim)) {
						LPDIRECT3DTEXTURE9 spr = d3d->mask2textureleds[i];
						if (!led && ledtypes[i] == LED_POWER && currprefs.power_led_dim)
							spr = d3d->mask2textureled_power_dim;
						if (spr) {
							v.x = (FLOAT)(d3d->mask2texture_offsetw / w + d3d->mask2textureledoffsets[i * 2 + 0]);
							v.y = (FLOAT)d3d->mask2textureledoffsets[i * 2 + 1];
							v.z = 0;
							d3d->sprite->Draw(spr, NULL, NULL, &v, 0xffffffff);
							d3d->sprite->Flush();
						}
					}
				}
			}

			MatrixScaling(&t, 1, 1, 0);
			d3d->sprite->SetTransform(&t);

			if (d3d->mask2texture_offsetw > 0) {
				v.x = 0;
				v.y = 0;
				r.left = 0;
				r.top = 0;
				r.right = (LONG)d3d->mask2texture_offsetw + 1;
				r.bottom = d3d->window_h;
				d3d->sprite->Draw (d3d->blanktexture, &r, NULL, &v, 0xffffffff);
				if (d3d->window_w > d3d->mask2texture_offsetw + d3d->mask2texture_ww) {
					v.x = d3d->mask2texture_offsetw + d3d->mask2texture_ww;
					v.y = 0;
					r.left = 0;
					r.top = 0;
					r.right = (LONG)(d3d->window_w - (d3d->mask2texture_offsetw + d3d->mask2texture_ww) + 1);
					r.bottom = (LONG)d3d->window_h;
					d3d->sprite->Draw(d3d->blanktexture, &r, NULL, &v, 0xffffffff);
				}
			}

		}
		if (d3d->ledtexture && (((currprefs.leds_on_screen & STATUSLINE_RTG) && WIN32GFX_IsPicassoScreen(mon)) || ((currprefs.leds_on_screen & STATUSLINE_CHIPSET) && !WIN32GFX_IsPicassoScreen(mon)))) {
			int slx, sly;
			statusline_getpos(d3d->num, &slx, &sly, d3d->window_w, d3d->window_h);
			v.x = (float)slx;
			v.y = (float)sly;
			v.z = 0.0f;
			d3d->sprite->Draw(d3d->ledtexture, NULL, NULL, &v, 0xffffffff);
		}
		struct d3d9overlay *ov = d3d->extoverlays;
		while (ov) {
			if (ov->tex) {
				v.x = (float)ov->x;
				v.y = (float)ov->y;
				v.z = 0.0f;
				d3d->sprite->Draw(ov->tex, NULL, NULL, &v, 0xffffffff);
			}
			ov = ov->next;
		}
		d3d->sprite->End();
	}
#endif

	hr = d3d->d3ddev->EndScene();
	if (FAILED (hr))
		write_log (_T("%s: EndScene() %s\n"), D3DHEAD, D3D_ErrorString (hr));
}

static void updatecursorsurface(int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	D3DLOCKED_RECT locked;
	int cx = (int)d3d->cursor_x;
	int cy = (int)d3d->cursor_y;
	int width = CURSORMAXWIDTH;
	int height = CURSORMAXHEIGHT;

	if (!d3d->cursorsurfaced3d || !d3d->cursorsurfaced3dtexbuf) {
		return;
	}

	if (d3d->cursorsurfaced3dtexbufupdated && cx >= 0 && cy >= 0 && cx + width <= d3d->tin_w && cy + height <= d3d->tin_h) {
		return;
	}

	d3d->cursorsurfaced3dtexbufupdated = false;
	HRESULT hr = d3d->cursorsurfaced3d->LockRect(0, &locked, NULL, 0);
	if (SUCCEEDED(hr)) {
		int pitch = locked.Pitch;
		uae_u8 *b = (uae_u8 *)locked.pBits;
		int cx = (int)d3d->cursor_x;
		int cy = (int)d3d->cursor_y;
		uae_u8 *s = d3d->cursorsurfaced3dtexbuf;
		for (int h = 0; h < CURSORMAXHEIGHT; h++) {
			int w = width;
			int x = 0;
			if (cx + w > d3d->tin_w) {
				w -= (cx + w) - d3d->tin_w;
			}
			if (cx < 0) {
				x = -cx;
				w -= -cx;
			}
			if (w <= 0 || cy + h > d3d->tin_h) {
				memset(b, 0, width * 4);
			} else {
				if (x > 0) {
					memset(b, 0, x * 4);
				}
				memcpy(b + x * 4, s + x * 4, w * 4);
				if (w < width) {
					memset(b + w * 4, 0, (width - w) * 4);
				}
			}
			b += pitch;
			s += width * 4;
		}
		d3d->cursorsurfaced3d->UnlockRect(0);

		if (cx >= 0 && cy >= 0 && cx + width <= d3d->tin_w && cy + height <= d3d->tin_h) {
			d3d->cursorsurfaced3dtexbufupdated = true;
		}
	}
}

static bool xD3D_setcursor(int monid, int x, int y, int width, int height, float mx, float my, bool visible, bool noscale)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (width < 0 || height < 0) {
		return xD3D_goodenough() > 0;
	}

	if (width && height) {
		d3d->cursor_offset2_x = d3d->cursor_offset_x;
		d3d->cursor_offset2_y = d3d->cursor_offset_y;
		d3d->cursor_x = (float)x;
		d3d->cursor_y = (float)y;
		d3d->cursor_mx = mx * d3d->xmult;
		d3d->cursor_my = my * d3d->ymult;
	} else {
		d3d->cursor_x = d3d->cursor_y = 0;
		d3d->cursor_offset2_x = d3d->cursor_offset2_y = 0;
	}
	d3d->cursor_scale = false; // !noscale;
	d3d->cursor_v = visible;

	updatecursorsurface(monid);

	return true;
}

static void xD3D_flushtexture(int monid, int miny, int maxy)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (d3d->fakemode || d3d->fulllocked || !d3d->texture1 || d3d->renderdisabled)
		return;
	if (miny >= 0 && maxy >= 0) {
		RECT r;
		maxy++;
		r.left = 0;
		r.right = d3d->tin_w;
		r.top = miny <= 0 ? 0 : miny;
		r.bottom = maxy <= d3d->tin_h ? maxy : d3d->tin_h;
		if (r.top <= r.bottom) {
			HRESULT hr = d3d->texture1->AddDirtyRect (&r);
			if (FAILED (hr))
				write_log (_T("%s: AddDirtyRect(): %s\n"), D3DHEAD, D3D_ErrorString (hr));
			//write_log (_T("%d %d\n"), r.top, r.bottom);
		}
	}
}

static void xD3D_unlocktexture(int monid, int y_start, int y_end)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	HRESULT hr;

	if (!isd3d(d3d)) {
		return;
	}
		
	if (d3d->fakelock) {
		d3d->fakelock--;
		return;
	}

	if (!d3d->texture1)
		return;

	if (d3d->locked) {
		LPDIRECT3DTEXTURE9 tex = d3d->texture1;
		if (d3d->locked == 2) {
			tex = d3d->texture2;
		}
		if (currprefs.leds_on_screen & (STATUSLINE_CHIPSET | STATUSLINE_RTG))
			updateleds(d3d);
		hr = tex->UnlockRect(0);
		if (y_start >= 0)
			xD3D_flushtexture(monid, y_start, y_end);
	}
	d3d->locked = 0;
	d3d->fulllocked = 0;
}

static uae_u8 *xD3D_locktexture (int monid, int *pitch, int *width, int *height, int fullupdate)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	D3DLOCKED_RECT lock;
	HRESULT hr;

	if (d3d->fakemode) {
		*pitch = 0;
		return d3d->fakebitmap;
	}

	if (fullupdate < 0) {
		if (d3d->usetexture == d3d->texture1) {
			LPDIRECT3DTEXTURE9 tex1 = d3d->texture1;
			LPDIRECT3DTEXTURE9 tex2 = d3d->texture2;
			IDirect3DSurface9* s1, * s2;
			if (SUCCEEDED(tex1->GetSurfaceLevel(0, &s1))) {
				if (SUCCEEDED(tex2->GetSurfaceLevel(0, &s2))) {
					HRESULT hr = d3d->d3ddev->StretchRect(s1, NULL, s2, NULL, D3DTEXF_NONE);
					s2->Release();
					d3d->usetexture = tex2;
				}
				s1->Release();
			}
		}
	} else {
		d3d->usetexture = d3d->texture1;
	}

	if (D3D_needreset (d3d) > 0) {
		return NULL;
	}
	if (!isd3d (d3d))
		return NULL;

	if (d3d->locked) {
		write_log (_T("%s: texture already locked!\n"), D3DHEAD);
		return NULL;
	}

	if (!d3d->texture1) {
		*pitch = 0;
		d3d->fakelock++;
		return d3d->fakebitmap;
	}

	lock.pBits = NULL;
	lock.Pitch = 0;
	hr = d3d->texture1->LockRect (0, &lock, NULL, fullupdate > 0 ? D3DLOCK_DISCARD : D3DLOCK_NO_DIRTY_UPDATE);
	if (FAILED (hr)) {
		write_log (_T("%s: LockRect failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
		return NULL;
	}
	d3d->locked = 1;
	if (lock.pBits == NULL || lock.Pitch == 0) {
		write_log (_T("%s: LockRect returned NULL texture\n"), D3DHEAD);
		D3D_unlocktexture(monid, -1, -1);
		return NULL;
	}
	d3d->fulllocked = fullupdate > 0;
	*pitch = lock.Pitch;
	if (height)
		*height = d3d->tin_h;
	if (width)
		*width = d3d->tin_w;
	return (uae_u8*)lock.pBits;
}

static void flushgpu (struct d3dstruct *d3d, bool wait)
{
	if (currprefs.turbo_emulation)
		return;
	if (d3d->query) {
		HRESULT hr = d3d->query->Issue (D3DISSUE_END);
		if (SUCCEEDED (hr)) {
			while (d3d->query->GetData (NULL, 0, D3DGETDATA_FLUSH) == S_FALSE) {
				if (!wait)
					return;
			}
		} else {
			static int reported;
			if (reported < 10) {
				reported++;
				write_log (_T("%s: query->Issue (D3DISSUE_END) failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			}
		}
	}
}

static bool xD3D_renderframe(int monid, int mode, bool immediate)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	static int vsync2_cnt;

	d3d->frames_since_init++;

	if (d3d->fakemode)
		return true;

	if (!isd3d (d3d) || !d3d->texture1 || !d3d->texture2)
		return false;

	if (d3d->filenotificationhandle != NULL) {
		bool notify = false;
		while (WaitForSingleObject (d3d->filenotificationhandle, 0) == WAIT_OBJECT_0) {
			if (FindNextChangeNotification (d3d->filenotificationhandle)) {
				if (d3d->frames_since_init > 50)
					notify = true;
			}
		}
		if (notify) {
			d3d->devicelost = 2;
			write_log (_T("%s: Shader file modification notification\n"), D3DHEAD);
		}
	}

	if (d3d->vsync2 > 0) {
		vsync2_cnt ^= 1;
		if (vsync2_cnt == 0)
			return true;
	}

	D3D_render2 (d3d, mode, monid);
	flushgpu (d3d, immediate);

	return true;
}

static void xD3D_showframe (int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (!isd3d (d3d))
		return;
	if (currprefs.turbo_emulation) {
		static int frameskip;
		static int toggle;
		if (frameskip-- > 0)
			return;
		frameskip = 10 + toggle;
		toggle = !toggle;
		D3D_showframe2 (d3d, false);
	} else {
		D3D_showframe2 (d3d, true);
		if (d3d->vsync2 == -1 && !currprefs.turbo_emulation) {
			D3D_showframe2 (d3d ,true);
		}
		flushgpu(d3d, true);
	}
}

static void xD3D_showframe_special (int monid, int mode)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	HRESULT hr;
	if (!isd3d (d3d))
		return;
	if (currprefs.turbo_emulation)
		return;
	if (pause_emulation)
		return;
	if (mode == 2) {
		hr = d3d->d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, d3ddebug ? 0x80 : 0, 0), 0, 0);
	}
	if (mode == 1) {
		D3D_showframe2(d3d, true);
	}
	flushgpu(d3d, true);
}

static void xD3D_refresh (int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];

	if (!isd3d (d3d))
		return;
	createscanlines(d3d, 0);
	for (int i = 0; i < 3; i++) {
		D3D_render2(d3d, true, monid);
		D3D_showframe2(d3d, true);
	}
}

void D3D_getpixelformat (int depth, int *rb, int *gb, int *bb, int *rs, int *gs, int *bs, int *ab, int *as, int *a)
{
	switch (depth)
	{
	case 32:
		*rb = 8;
		*gb = 8;
		*bb = 8;
		*ab = 8;
		*rs = 16;
		*gs = 8;
		*bs = 0;
		*as = 24;
		*a = 0;
		break;
	case 15:
		*rb = 5;
		*gb = 5;
		*bb = 5;
		*ab = 1;
		*rs = 10;
		*gs = 5;
		*bs = 0;
		*as = 15;
		*a = 0;
	break;
	case 16:
		*rb = 5;
		*gb = 6;
		*bb = 5;
		*ab = 0;
		*rs = 11;
		*gs = 5;
		*bs = 0;
		*as = 0;
		*a = 0;
		break;
	}
}

static float xD3D_getrefreshrate(int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	HRESULT hr;
	D3DDISPLAYMODE dmode;

	waitfakemode (d3d);
	if (!isd3d (d3d))
		return -1.0f;
	hr = d3d->d3ddev->GetDisplayMode (0, &dmode);
	if (FAILED (hr))
		return -1.0f;
	return (float)dmode.RefreshRate;
}

static void xD3D_guimode(int monid, int guion)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	HRESULT hr;

	if (guion != 0 && guion != 1)
		return;

	waitfakemode (d3d);
	if (!isd3d (d3d))
		return;
	D3D_render2(d3d, true, monid);
	D3D_showframe2(d3d, true);
	hr = d3d->d3ddev->SetDialogBoxMode (guion ? TRUE : FALSE);
	if (FAILED (hr))
		write_log (_T("%s: SetDialogBoxMode %s\n"), D3DHEAD, D3D_ErrorString (hr));
	d3d->guimode = guion;
}

LPDIRECT3DSURFACE9 D3D_capture(int monid, int *w, int *h, int *bits, bool rendertarget)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	HRESULT hr;
	LPDIRECT3DSURFACE9 ret = NULL;

	waitfakemode(d3d);
	if (!isd3d(d3d))
		return NULL;
	if (rendertarget) {
		LPDIRECT3DSURFACE9 rt;
		hr = d3d->d3ddev->GetRenderTarget(0, &rt);
		if (FAILED(hr)) {
			write_log(_T("%s: D3D_capture() GetRenderTarget() failed: %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return NULL;
		}
		hr = d3d->d3ddev->GetRenderTargetData(rt, d3d->screenshotsurface);
		rt->Release();
		if (FAILED(hr)) {
			write_log(_T("%s: D3D_capture() GetRenderTargetData() failed: %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return NULL;
		}
		ret = d3d->screenshotsurface;
	} else {
		hr = d3d->texture1->GetSurfaceLevel(0, &ret);
		if (FAILED(hr)) {
			write_log(_T("%s: D3D_capture() GetSurfaceLevel() failed: %s\n"), D3DHEAD, D3D_ErrorString(hr));
			return NULL;
		}
	}
	*w = d3d->window_w;
	*h = d3d->window_h;
	*bits = d3d->t_depth;
	return ret;
}

static HDC xD3D_getDC(int monid, HDC hdc)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	static LPDIRECT3DSURFACE9 bb;
	HRESULT hr;

	waitfakemode (d3d);
	if (!isd3d (d3d))
		return 0;
	if (!hdc) {
		hr = d3d->d3ddev->GetBackBuffer (0, 0, D3DBACKBUFFER_TYPE_MONO, &bb);
		if (FAILED (hr)) {
			write_log (_T("%s: GetBackBuffer() failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
			return 0;
		}
		hr = bb->GetDC (&hdc);
		if (SUCCEEDED (hr))
			return hdc;
		write_log (_T("%s: GetDC() failed: %s\n"), D3DHEAD, D3D_ErrorString (hr));
	}
	if (bb) {
		if (hdc)
			bb->ReleaseDC (hdc);
		bb->Release ();
		bb = NULL;
	}
	return 0;
}

static int xD3D_isenabled(int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	return d3d->d3d_enabled ? 1 : 0;
}

static uae_u8 *xD3D_setcursorsurface(int monid, bool query, int *pitch)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	if (query) {
		return d3d->cursorsurfaced3dtexbuf;
	}
	if (pitch) {
		*pitch = CURSORMAXWIDTH * 4;
		return d3d->cursorsurfaced3dtexbuf;
	}
	d3d->cursorsurfaced3dtexbufupdated = false;
	return NULL;
}

static bool xD3D_getscanline(int *scanline, bool *invblank)
{
	struct d3dstruct *d3d = &d3ddata[0];
	HRESULT hr;
	D3DRASTER_STATUS rt;

	if (!isd3d(d3d))
		return false;
	if (d3d->d3dswapchain)
		hr = d3d->d3dswapchain->GetRasterStatus(&rt);
	else
		hr = d3d->d3ddev->GetRasterStatus(0, &rt);
	if (FAILED(hr))
		return false;
	*scanline = rt.ScanLine;
	*invblank = rt.InVBlank != FALSE;
	return true;
}

static bool xD3D_run(int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	return false;
}

static bool xD3D_extoverlay(struct extoverlay *ext, int monid)
{
	struct d3dstruct *d3d = &d3ddata[monid];
	struct d3d9overlay *ov, *ovprev, *ov2;
	LPDIRECT3DTEXTURE9 s;
	D3DLOCKED_RECT locked;
	HRESULT hr;

	s = NULL;
	ov = d3d->extoverlays;
	ovprev = NULL;
	while (ov) {
		if (ov->id == ext->idx) {
			s = ov->tex;
			break;
		}
		ovprev = ov;
		ov = ov->next;
	}

	if (!s && (ext->width <= 0 || ext->height <= 0))
		return false;

	if (!ext->data && s && (ext->width == 0 || ext->height == 0)) {
		ov->x = ext->xpos;
		ov->y = ext->ypos;
		return true;
	}

	if (ov && s) {
		if (ovprev) {
			ovprev->next = ov->next;
		} else {
			d3d->extoverlays = ov->next;
		}
		s->Release();
		xfree(ov);
		if (ext->width <= 0 || ext->height <= 0)
			return true;
	}

	if (ext->width <= 0 || ext->height <= 0)
		return false;

	ov = xcalloc(d3d9overlay, 1);
	s = createtext(d3d, ext->width, ext->height, D3DFMT_A8R8G8B8);
	if (!s) {
		xfree(ov);
		return false;
	}

	ov->tex = s;
	ov->id = ext->idx;

	ov2 = d3d->extoverlays;
	ovprev = NULL;
	for (;;) {
		if (ov2 == NULL || ov2->id >= ov->id) {
			if (ov2 == d3d->extoverlays) {
				d3d->extoverlays = ov;
				ov->next = ov2;
			} else {
				ov->next = ovprev->next;
				ovprev->next = ov;
			}
			break;
		}
		ovprev = ov2;
		ov2 = ov2->next;
	}

	ov->x = ext->xpos;
	ov->y = ext->ypos;

	hr = s->LockRect(0, &locked, NULL, 0);
	if (SUCCEEDED(hr)) {
		for (int y = 0; y < ext->height; y++) {
			memcpy((uae_u8*)locked.pBits + y * locked.Pitch, ext->data + y * ext->width * 4, ext->width * 4);
		}
		s->UnlockRect(0);
	}

	return true;
}

void d3d9_select(void)
{
	for (int i = 0; i < MAX_AMIGAMONITORS; i++) {
		d3ddata[i].num = i;
	}
	D3D_free = xD3D_free;
	D3D_init = xD3D_init;
	D3D_alloctexture = xD3D_alloctexture;
	D3D_refresh = xD3D_refresh;
	D3D_renderframe = xD3D_renderframe;
	D3D_showframe = xD3D_showframe;
	D3D_showframe_special = xD3D_showframe_special;
	D3D_locktexture = xD3D_locktexture;
	D3D_unlocktexture = xD3D_unlocktexture;
	D3D_flushtexture = xD3D_flushtexture;
	D3D_guimode = xD3D_guimode;
	D3D_getDC = xD3D_getDC;
	D3D_isenabled = xD3D_isenabled;
	D3D_clear = xD3D_clear;
	D3D_canshaders = xD3D_canshaders;
	D3D_goodenough = xD3D_goodenough;
	D3D_setcursor = xD3D_setcursor;
	D3D_setcursorsurface = xD3D_setcursorsurface;
	D3D_getrefreshrate = xD3D_getrefreshrate;
	D3D_restore = xD3D_restore;
	D3D_resize = NULL;
	D3D_change = NULL;
	D3D_getscalerect = xD3D_getscalerect;
	D3D_run = xD3D_run;
	D3D_debug = xD3D_debug;
	D3D_led = xD3D_led;
	D3D_getscanline = xD3D_getscanline;
	D3D_extoverlay = xD3D_extoverlay;
	D3D_paint = NULL;
}

#endif
