/*  ZeroGS
 *  Copyright (C) 2005-2006 zerofrog@gmail.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#if defined(_WIN32) || defined(__WIN32__)
#include <d3dx9.h>
#include <dxerr9.h>
#include <aviUtil.h>
#endif

#include <stdio.h>

#include <malloc.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>

#include "GS.h"
#include "Mem.h"
#include "x86.h"
#include "Regs.h"
#include "zerogs.h"
#include "resource.h"

#include "zerogsshaders/zerogsshaders.h"
#include "targets.h"

#define DEBUG_PS2		0

#define POINT_BUFFERFLUSH				512
#define POINT_BUFFERSIZE				(1<<18)

#define MINMAX_SHIFT	3
#define MAX_ACTIVECLUTS					16

#define ZEROGS_SAVEVER 0xaa000005

#define STENCIL_ALPHABIT	1		// if set, dest alpha >= 0x80
#define STENCIL_PIXELWRITE	2		// if set, pixel just written (reset after every Flush)
#define STENCIL_FBA			4		// if set, just written pixel's alpha >= 0 (reset after every Flush)
#define STENCIL_SPECIAL		8		// if set, indicates that pixel passed its alpha test (reset after every Flush)
//#define STENCIL_PBE			16
#define STENCIL_CLEAR		(2|4|8|16)

#define VBSAVELIMIT ((u32)((u8*)&vb[0].nNextFrameHeight-(u8*)&vb[0]))

using namespace ZeroGS;

static LPDIRECT3D9 pD3D = NULL;		// Used to create the D3DDevice
LPDIRECT3DDEVICE9 pd3dDevice = NULL;
static DXVEC4 s_vznorm;
extern u32 g_nGenVars, g_nTexVars, g_nAlphaVars, g_nResolve;
extern char *libraryName;
extern int g_nFrame, g_nRealFrame;
extern float fFPS;
extern unsigned char revision, build, minor;

BOOL g_bDisplayMsg = 1;

extern HINSTANCE hInst;

BOOL g_bSaveFlushedFrame = 1;
BOOL g_bIsLost = 0;
int g_nFrameRender = 10;
int g_nFramesSkipped = 0;
int g_MaxRenderedHeight = 0;

#ifdef RELEASE_TO_PUBLIC

#define INC_GENVARS()
#define INC_TEXVARS()
#define INC_ALPHAVARS()
#define INC_RESOLVE()

#define g_bUpdateEffect 0
#define g_bWriteProfile 0
#define g_bSaveTex 0
#define g_bSaveTrans 0
#define g_bSaveFrame 0
#define g_bSaveFinalFrame 0
#define g_bUpdateStencil 1
#define g_bSaveResolved 0

#else

#define INC_GENVARS() ++g_nGenVars
#define INC_TEXVARS() ++g_nTexVars
#define INC_ALPHAVARS() ++g_nAlphaVars
#define INC_RESOLVE() ++g_nResolve

BOOL g_bSaveTrans = 0;
BOOL g_bUpdateEffect = 0;
BOOL g_bWriteProfile = 0;
BOOL g_bSaveTex = 0;	// saves the curent texture
BOOL g_bSaveFrame = 0;	// saves the current psurfTarget
BOOL g_bSaveFinalFrame = 0; // saves the input to the CRTC
BOOL g_bUpdateStencil = 1;		// only needed for dest alpha test (unfortunately, it has to be on all the time)	
BOOL g_bSaveResolved = 0;

#endif

#define DRAW() pd3dDevice->DrawPrimitive(primtype[curvb.curprim.prim], 0, curvb.dwCount)

extern int s_frameskipping;
			
//inline void SetRenderTarget_(int index, LPD3DSURF psurf, int counter, const char* pname)
//{
//	static LPD3DSURF ptargs[4] = {NULL};
//	static int counters[4] = {0};
//	static const char* pnames[4] = {NULL};
//
//	if( ptargs[index] == psurf && psurf != NULL )
//		DEBUG_LOG("duplicate targets\n");
//	pd3dDevice->SetRenderTarget(index, psurf);
//	ptargs[index] = psurf;
//	counters[index] = counter;
//	pnames[index] = pname;
//}
//
//#define SetRenderTarget(index, psurf) SetRenderTarget_(index, psurf, __COUNTER__, __FUNCTION__)

static u32 g_SaveFrameNum = 0;
BOOL g_bMakeSnapshot = 0;
string strSnapshot;

int GPU_TEXWIDTH = 512;
float g_fiGPU_TEXWIDTH = 1/512.0f;

int g_MaxTexWidth = 4096, g_MaxTexHeight = 4096;

// AVI Capture
static int s_aviinit = 0;
static int s_avicapturing = 0;
static LPD3DSURF s_ptexAVICapture = NULL; // system memory texture

const u32 g_primmult[8] = { 1, 2, 2, 3, 3, 3, 2, 0xff };
const u32 g_primsub[8] = { 1, 2, 1, 3, 1, 1, 2, 0 };

inline DWORD FtoDW(float f) { return (*((DWORD*)&f)); }

float g_fBlockMult = 1;
static int s_nFullscreen = 0;
int g_nDepthUpdateCount = 0;
int g_nDepthBias = 0;

// Consts
static const D3DPRIMITIVETYPE primtype[8] = { D3DPT_POINTLIST, D3DPT_LINELIST, D3DPT_LINELIST, D3DPT_TRIANGLELIST, D3DPT_TRIANGLELIST, D3DPT_TRIANGLELIST, D3DPT_TRIANGLELIST, D3DPT_FORCE_DWORD };
static const DWORD blendalpha[3] = { D3DBLEND_SRCALPHA, D3DBLEND_DESTALPHA, D3DBLEND_BLENDFACTOR };
static const DWORD blendinvalpha[3] = { D3DBLEND_INVSRCALPHA, D3DBLEND_INVDESTALPHA, D3DBLEND_INVBLENDFACTOR };

static const int PRIMMASK = 0x0e;	// for now ignore 0x10 (AA)

static const DWORD g_dwAlphaCmp[] = { D3DCMP_NEVER, D3DCMP_ALWAYS, D3DCMP_LESS, D3DCMP_LESSEQUAL, 
								D3DCMP_EQUAL, D3DCMP_GREATEREQUAL, D3DCMP_GREATER, D3DCMP_NOTEQUAL };

// used for afail case
static const DWORD g_dwReverseAlphaCmp[] = { D3DCMP_ALWAYS, D3DCMP_NEVER, D3DCMP_GREATEREQUAL, D3DCMP_GREATER, 
									D3DCMP_NOTEQUAL, D3DCMP_LESS, D3DCMP_LESSEQUAL, D3DCMP_EQUAL };

static const DWORD g_dwZCmp[] = { D3DCMP_NEVER, D3DCMP_ALWAYS, D3DCMP_GREATEREQUAL, D3DCMP_GREATER };

/////////////////////
// graphics resources
static LPD3DDECL pdecl = NULL;
static LPD3DVS pvs[16] = {NULL};
static LPD3DPS ppsRegular[4] = {NULL}, ppsTexture[NUM_SHADERS] = {NULL};
static LPD3DPS ppsCRTC[2] = {NULL}, ppsCRTC24[2] = {NULL}, ppsCRTCTarg[2] = {NULL};

int g_nPixelShaderVer = SHADER_30; // default

static BYTE* s_lpShaderResources = NULL;
static map<int, SHADERHEADER*> mapShaderResources;

LPD3DTEX s_ptexCurSet[2] = {NULL};

#define s_bForceTexFlush 1
static LPD3DTEX s_ptexNextSet[2] = {NULL};

static ID3DXFont* pFont = NULL;
static ID3DXSprite* pSprite = NULL;

static LPD3DSURF psurfOrgTarg = NULL, psurfOrgDepth = NULL;

LPD3DTEX ptexBlocks = NULL, ptexConv16to32 = NULL;		// holds information on block tiling
LPD3DTEX ptexBilinearBlocks = NULL;
IDirect3DVolumeTexture9* ptexConv32to16 = NULL;
static LPD3DTEX s_ptexInterlace = NULL;			// holds interlace fields
static int s_nInterlaceTexWidth = 0;					// width of texture
static list<LPD3DTEX> s_vecTempTextures;			// temporary textures, released at the end of every frame

static BOOL s_bTexFlush = FALSE;
static LPD3DTEX ptexLogo = NULL;
static BOOL s_bWriteDepth = FALSE;
static BOOL s_bDestAlphaTest = FALSE;
static int s_nLastResolveReset = 0;
static int s_nResolveCounts[30] = {0}; // resolve counts for last 30 frames
static int s_nCurResolveIndex = 0;
int s_nResolved = 0; // number of targets resolved this frame 
int g_nDepthUsed = 0; // ffx2 pal movies
static int s_nWriteDepthCount = 0;
static int s_nWireframeCount = 0;
static int s_nWriteDestAlphaTest = 0;

////////////////////
// State parameters
static float fiRendWidth, fiRendHeight;	

static DWORD dwStencilRef, dwStencilMask;

static DXVEC4 vAlphaBlendColor;		// used for GPU_COLOR

static BYTE bNeedBlendFactorInAlpha;		// set if the output source alpha is different from the real source alpha (only when blend factor > 0x80)
static DWORD s_dwColorWrite = 0xf;			// the color write mask of the current target

BOOL g_bDisplayFPS = FALSE;

union {
	struct {
		BYTE _bNeedAlphaColor;		// set if vAlphaBlendColor needs to be set
		BYTE _b2XAlphaTest;			// Only valid when bNeedAlphaColor is set. if 1st bit set set, double all alpha testing values
									// otherwise alpha testing needs to be done separately.
		BYTE _bDestAlphaColor;		// set to 1 if blending with dest color (process only one tri at a time). If 2, dest alpha is always 1.
		BYTE _bAlphaClamping;		// if first bit is set, do min; if second bit, do max
	};
	u32 _bAlphaState;
} g_vars;

#define bNeedAlphaColor g_vars._bNeedAlphaColor
#define b2XAlphaTest g_vars._b2XAlphaTest
#define bDestAlphaColor g_vars._bDestAlphaColor
#define bAlphaClamping g_vars._bAlphaClamping

int g_PrevBitwiseTexX = -1, g_PrevBitwiseTexY = -1; // textures stored in SAMP_BITWISEANDX and SAMP_BITWISEANDY

// stores the buffers for the last RenderCRTC
const float g_filog32 = 0.999f / (32.0f * logf(2.0f));

static BOOL s_bAlphaSet = FALSE;
static alphaInfo s_alphaInfo;

namespace ZeroGS
{
	VB vb[2];
	float fiTexWidth[2], fiTexHeight[2];	// current tex width and height
	LONG width, height;
	u8* g_pbyGSMemory = NULL;	// 4Mb GS system mem
	u8* g_pbyGSClut = NULL;

	D3DPRESENT_PARAMETERS d3dpp; 

	BYTE s_AAx = 0, s_AAy = 0; // if AAy is set, then AAx has to be set
	BYTE bIndepWriteMasks = 1;
	BOOL s_bBeginScene = FALSE;
	D3DFORMAT g_RenderFormat = D3DFMT_A16B16G16R16F;
	int icurctx = -1;

	LPD3DVB pvbRect = NULL;

	DXVEC4 g_vdepth = DXVEC4(65536.0f, 256.0f, 1.0f, 65536.0f*256.0f);

	LPD3DVS pvsBitBlt = NULL, pvsBitBlt30 = NULL;
	LPD3DPS ppsBitBlt[2] = {NULL}, ppsBitBltDepth[2] = {NULL}, ppsBitBltDepthTex[2] = {NULL}, ppsOne = NULL;
	LPD3DPS ppsBaseTexture = NULL, ppsConvert16to32 = NULL, ppsConvert32to16 = NULL;

	extern CRangeManager s_RangeMngr; // manages overwritten memory
	void FlushTransferRanges(const tex0Info* ptex);

	// returns the first and last addresses aligned to a page that cover 
	void GetRectMemAddress(int& start, int& end, int psm, int x, int y, int w, int h, int bp, int bw);

	HRESULT LoadEffects();
	HRESULT LoadExtraEffects();
	LPD3DPS LoadShadeEffect(int type, int texfilter, int fog, int testaem, int exactcolor, const clampInfo& clamp, int context);

	static int s_nNewWidth = -1, s_nNewHeight = -1;
	void ChangeDeviceSize(int nNewWidth, int nNewHeight);

	void ProcessMessages();
	void RenderCustom(float fAlpha); // intro anim

	struct MESSAGE
	{
		MESSAGE() {}
		MESSAGE(const char* p, DWORD dw) { strcpy(str, p); dwTimeStamp = dw; }
		char str[255];
		DWORD dwTimeStamp;
	};

	static list<MESSAGE> listMsgs;

	///////////////////////
	// Method Prototypes //
	///////////////////////

	void AdjustTransToAspect(DXVEC4& v, int dispwidth, int dispheight);

	void KickPoint();
	void KickLine();
	void KickTriangle();
	void KickTriangleFan();
	void KickSprite();
	void KickDummy();

	inline void SetContextTarget(int context);

	// use to update the d3d state
	void SetTexVariables(int context);
	void SetAlphaVariables(const alphaInfo& ainfo);
	void ResetAlphaVariables();

	__forceinline void SetAlphaTestInt(pixTest curtest);

	__forceinline void RenderAlphaTest(const VB& curvb);
	__forceinline void RenderStencil(const VB& curvb, DWORD dwUsingSpecialTesting);
	__forceinline void ProcessStencil(const VB& curvb);
	__forceinline void RenderFBA(const VB& curvb);
	__forceinline void ProcessFBA(const VB& curvb);

	void ResolveInRange(int start, int end);

	void ExtWrite();

	inline LPD3DTEX CreateInterlaceTex(int width) {
		if( width == s_nInterlaceTexWidth && s_ptexInterlace != NULL ) return s_ptexInterlace;

		SAFE_RELEASE(s_ptexInterlace);
		s_nInterlaceTexWidth = width;

		HRESULT hr;
		V(pd3dDevice->CreateTexture(width, 1, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &s_ptexInterlace, NULL));

		D3DLOCKED_RECT lock;
		s_ptexInterlace->LockRect(0, &lock, NULL, 0);
		for(int i = 0; i < width; ++i) ((u32*)lock.pBits)[i] = (i&1) ? 0xffffffff : 0;
		s_ptexInterlace->UnlockRect(0);

		return s_ptexInterlace;
	}

	DrawFn drawfn[8] = { KickDummy, KickDummy, KickDummy, KickDummy,
						KickDummy, KickDummy, KickDummy, KickDummy };

}; // end namespace

///////////////////
// Context State //
///////////////////
ZeroGS::VB::VB()
{
	memset(this, 0, sizeof(VB));
	tex0.tw = 1;
	tex0.th = 1;
}

ZeroGS::VB::~VB()
{
	Destroy();
}

void ZeroGS::VB::Destroy()
{
	Unlock();
	SAFE_RELEASE(pvb);
	
	prndr = NULL;
	pdepth = NULL;
}

void ZeroGS::VB::Lock()
{
	assert(pvb != NULL);
	if( pbuf == NULL )
	{
		if( dwCurOff+POINT_BUFFERFLUSH > POINT_BUFFERSIZE ) dwCurOff = 0;

		pvb->Lock(dwCurOff*sizeof(VertexGPU), sizeof(VertexGPU)*POINT_BUFFERFLUSH, (void**)&pbuf, dwCurOff ? D3DLOCK_NOOVERWRITE|D3DLOCK_NOSYSLOCK : D3DLOCK_DISCARD|D3DLOCK_NOSYSLOCK);
		dwCount = 0;
		assert( pbuf != NULL );
	}
}

bool ZeroGS::VB::CheckPrim()
{
	Lock();

	if( (PRIMMASK & prim->_val) != (PRIMMASK & curprim._val) || primtype[prim->prim] != primtype[curprim.prim] )
		return dwCount > 0;

	return false;
}

// upper bound on max possible height
#define GET_MAXHEIGHT(fbp, fbw, psm) ((((0x00100000-64*(fbp))/(fbw))&~0x1f)<<((psm&2)?1:0))

#include <set>
static int maxmin = 608;
//static set<int> s_setFBP[2]; // previous frame/zbuf pointers for the last 2 frames
//static int s_nCurFBPSet = 0;
//static map<int, int> s_mapFrameHeights[2];
//static int s_nCurFrameMap = 0;

// a lot of times, target is too big and overwrites the texture using, if tbp != 0, use it to bound
void ZeroGS::VB::CheckFrame(int tbp)
{
	static int bChanged;

	if( bNeedZCheck ) {
		PRIM_LOG("zbuf_%d: zbp=0x%x psm=0x%x, zmsk=%d\n", ictx, zbuf.zbp, zbuf.psm, zbuf.zmsk);
		//zbuf = *zb;
	}

	// invalid bpp
	if( m_Blocks[gsfb.psm].bpp == 0 ) {
		ERROR_LOG("CheckFrame invalid bpp %d\n", gsfb.psm);
		return;
	}

	bChanged = 0;

	if( gsfb.fbw <= 0 ) {
		return;
	}

	if( bNeedFrameCheck ) {

		int maxpos = 0x00100000;

		// important to set before calling GetTarg
		bNeedFrameCheck = 0;
		bNeedZCheck = 0;

		// add constraints of other targets
		if( gsfb.fbw > 0 ) {
			maxpos = 0x00100000-64*gsfb.fbp;

			// make sure texture is far away from tbp
			if( gsfb.fbp < tbp && gsfb.fbp + 0x2000 < tbp) {
				maxpos = min(64*(tbp-gsfb.fbp), maxpos);
			}
			if( prndr != NULL ) {
				// offroad uses 0x80 fbp which messes up targets
				if( gsfb.fbp + 0x80 < frame.fbp ) {
					// special case when double buffering (hamsterball)
					maxpos = min(64*(frame.fbp-gsfb.fbp), maxpos);
				}
			}
			if( zbuf.zbp < tbp && !zbuf.zmsk ) {
				maxpos = min((tbp-zbuf.zbp)*((zbuf.psm&2)?128:64), maxpos);
			}

			// old caching method
			if( gsfb.fbp < zbuf.zbp && !zbuf.zmsk ) { // zmsk necessary for KH movie
				int temp = 64*(zbuf.zbp-gsfb.fbp);//min( (0x00100000-64*zbuf.zbp) , 64*(zbuf.zbp-gsfb.fbp) );
				maxpos = min(temp, maxpos);
			}

			maxpos /= gsfb.fbw;
			if( gsfb.psm & 2 )
				maxpos *= 2;

			maxpos = min(gsfb.fbh, maxpos);
			maxpos = min(maxmin, maxpos);
			//? atelier iris crashes without it
			if( maxpos > 256 )
				maxpos &= ~0x1f;
		}
		else {
			ERROR_LOG("render target null, ignoring\n");
			//prndr = NULL;
			//pdepth = NULL;
			return;
		}

		gsfb.psm &= 0xf; // shadow tower

		if( prndr != NULL ) {

			// render target
			if( prndr->psm != gsfb.psm ) {
				// behavior for dest alpha varies
				ResetAlphaVariables();
			}
		}
	
		int fbh = (scissor.y1>>MINMAX_SHIFT)+1;
		if( fbh > 2 && (fbh&1) ) fbh -= 1;

		if( !(gsfb.psm&2) || !(g_GameSettings&GAME_FULL16BITRES) ) {
			fbh = min(fbh, maxpos);
		}

		frame = gsfb;
		if (frame.fbw > 1024) frame.fbw = 1024;

//		if( fbh > 256 && (fbh % m_Blocks[gsfb.psm].height) <= 2 ) {
//			// dragon ball z
//			fbh -= fbh%m_Blocks[gsfb.psm].height;
//		}
		if( !(frame.psm&2) || !(g_GameSettings&GAME_FULL16BITRES) )
			frame.fbh = fbh;

		if( !(frame.psm&2) ) {//|| !(g_GameSettings&GAME_FULL16BITRES) ) {
			if( frame.fbh >= 512 ) {
				// neopets hack
				maxmin = min(maxmin, frame.fbh);
				frame.fbh = maxmin;
			}
		}

		// mgs3 hack to get proper resolution, targets after 0x2000 are usually feedback
		if( g_MaxRenderedHeight >= 0xe0 && frame.fbp >= 0x2000 ) {
			int considerheight = (g_MaxRenderedHeight/8+31)&~31;
			if( frame.fbh > considerheight )
				frame.fbh = considerheight;
			else if( frame.fbh <= 32 )
				frame.fbh = considerheight;
			
			if( frame.fbh == considerheight ) {
				// stops bad resolves (mgs3)
				if( !curprim.abe && (!test.ate || test.atst == 0) )
					s_nResolved |= 0x100;
			}
		}

		// ffxii hack to stop resolving
		if( !(frame.psm&2) || !(g_GameSettings&GAME_FULL16BITRES) ) {
			if( frame.fbp >= 0x3000 && fbh >= 0x1a0 ) {
				int endfbp = frame.fbp + frame.fbw*fbh/((gsfb.psm&2)?128:64);

				// see if there is a previous render target in the way, reduce
				for(CRenderTargetMngr::MAPTARGETS::iterator itnew = s_RTs.mapTargets.begin(); itnew != s_RTs.mapTargets.end(); ++itnew) {
					if( itnew->second->fbp > frame.fbp && endfbp > itnew->second->fbp ) {
						endfbp = itnew->second->fbp;
					}
				}

				frame.fbh = (endfbp-frame.fbp)*((gsfb.psm&2)?128:64)/frame.fbw;
			}
		}

		CRenderTarget* pprevrndr = prndr;
		CDepthTarget* pprevdepth = pdepth;

		// reset so that Resolve doesn't call Flush
		prndr = NULL;
		pdepth = NULL;

		CRenderTarget* pnewtarg = s_RTs.GetTarg(frame, 0, maxmin);
		assert( pnewtarg != NULL );

		// pnewtarg->fbh >= 0x1c0 needed for ffx
		if( pnewtarg->fbh >= 0x1c0 && pnewtarg->fbh > frame.fbh && zbuf.zbp < tbp && !zbuf.zmsk ) {
			// check if zbuf is in the way of the texture (suikoden5)
			int maxallowedfbh = (tbp-zbuf.zbp)*((zbuf.psm&2)?128:64) / gsfb.fbw;
			if( gsfb.psm & 2 )
				maxallowedfbh *= 2;

			if( pnewtarg->fbh > maxallowedfbh+32 ) { // +32 needed for ffx2
				// destroy and recreate
				s_RTs.DestroyAll(0, 0x100, pnewtarg->fbw);
				pnewtarg = s_RTs.GetTarg(frame, 0, maxmin);
				assert( pnewtarg != NULL );
			}
		}

		PRIM_LOG("frame_%d: fbp=0x%x fbw=%d fbh=%d(%d) psm=0x%x fbm=0x%x\n", ictx, gsfb.fbp, gsfb.fbw, gsfb.fbh, pnewtarg->fbh, gsfb.psm, gsfb.fbm);

		if( (pprevrndr != pnewtarg) || (prndr != NULL && (prndr->status & CRenderTarget::TS_NeedUpdate)) )
			bChanged = 1;

		prndr = pnewtarg;

		// update z
		frameInfo tempfb;
		tempfb.fbw = prndr->fbw;
		tempfb.fbp = zbuf.zbp;
		tempfb.psm = zbuf.psm;
		tempfb.fbh = prndr->targheight;
		if( zbuf.psm == 0x31 ) tempfb.fbm = 0xff000000;
		else tempfb.fbm = 0;

		// check if there is a target that exactly aligns with zbuf (zbuf can be cleared this way, gunbird 2)
		//u32 key = zbuf.zbp|(frame.fbw<<16);
		//CRenderTargetMngr::MAPTARGETS::iterator it = s_RTs.mapTargets.find(key);
//		if( it != s_RTs.mapTargets.end() ) {
//#ifdef _DEBUG
//			DEBUG_LOG("zbuf resolve\n");
//#endif
//			if( it->second->status & CRenderTarget::TS_Resolved )
//				it->second->Resolve();
//		}

		CDepthTarget* pnewdepth = (CDepthTarget*)s_DepthRTs.GetTarg(tempfb, CRenderTargetMngr::TO_DepthBuffer |
			CRenderTargetMngr::TO_StrictHeight|(zbuf.zmsk?CRenderTargetMngr::TO_Virtual:0),
			prndr->targheight);//GET_MAXHEIGHT(zbuf.zbp, gsfb.fbw, 0));
		assert( pnewdepth != NULL && prndr != NULL );
		assert( pnewdepth->fbh == prndr->targheight );

		if( (pprevdepth != pnewdepth) || (pdepth != NULL && (pdepth->status & CRenderTarget::TS_NeedUpdate)) )
			bChanged |= 2;

		pdepth = pnewdepth;

		if( prndr->status & CRenderTarget::TS_NeedConvert32) {
			if( pdepth->pdepth != NULL )
				pd3dDevice->SetDepthStencilSurface(pdepth->pdepth);
			prndr->fbh *= 2;
			prndr->targheight *= 2;
			prndr->ConvertTo32();
			prndr->status &= ~CRenderTarget::TS_NeedConvert32;
		}
		else if( prndr->status & CRenderTarget::TS_NeedConvert16 ) {
			if( pdepth->pdepth != NULL )
				pd3dDevice->SetDepthStencilSurface(pdepth->pdepth);
			prndr->fbh /= 2;
			prndr->targheight /= 2;
			prndr->ConvertTo16();
			prndr->status &= ~CRenderTarget::TS_NeedConvert16;
		}
	}
	else if( bNeedZCheck  ) {

		bNeedZCheck = 0;
		CDepthTarget* pprevdepth = pdepth;
		pdepth = NULL;

		if( prndr != NULL && gsfb.fbw > 0 ) {
			// just z changed
			frameInfo f;
			f.fbp = zbuf.zbp;
			f.fbw = prndr->fbw;
			f.fbh = prndr->fbh;
			f.psm = zbuf.psm;
			
			if( zbuf.psm == 0x31 ) f.fbm = 0xff000000;
			else f.fbm = 0;
			CDepthTarget* pnewdepth = (CDepthTarget*)s_DepthRTs.GetTarg(f, CRenderTargetMngr::TO_DepthBuffer|CRenderTargetMngr::TO_StrictHeight|
				(zbuf.zmsk?CRenderTargetMngr::TO_Virtual:0), prndr->fbh);//GET_MAXHEIGHT(zbuf.zbp, gsfb.fbw, 0));

			assert( pnewdepth != NULL && prndr != NULL );
			assert( pnewdepth->fbh == prndr->fbh );

			if( (pprevdepth != pnewdepth) || (pdepth != NULL && (pdepth->status & CRenderTarget::TS_NeedUpdate)) )
				bChanged = 2;

			pdepth = pnewdepth;
		}
	}
	
	s_nResolved &= 0xff; // restore

	if( prndr != NULL )
		SetContextTarget(ictx);

	//if( prndr != NULL && ictx == icurctx) 
	//else bVarsSetTarg = 0;

//	if( prndr != NULL && bChanged ) {
//		if( ictx == icurctx ) SetContextTarget(icurctx);
//		else
//			bVarsSetTarg = 0;
//	}
}

void ZeroGS::VB::FlushTexData()
{
	assert( bNeedTexCheck );

	bNeedTexCheck = 0;

	u32 psm = (uNextTex0Data[0] >> 20) & 0x3f;
	if( psm == 9 ) psm = 1; // hmm..., ffx intro menu

	// don't update unless necessary
	if( uCurTex0Data[0] == uNextTex0Data[0] && (uCurTex0Data[1]&0x1f) == (uNextTex0Data[1]&0x1f) ) {

		if( PSMT_ISCLUT(psm) ) {
			
			// have to write the CLUT again if changed
			if( (uCurTex0Data[1]&0x1fffffe0) == (uNextTex0Data[1]&0x1fffffe0) ) {

				if( uNextTex0Data[1]&0xe0000000 ) {
					//ZeroGS::Flush(ictx);
					ZeroGS::texClutWrite(ictx);
					// invalidate to make sure target didn't change!
					bVarsTexSync = FALSE;
				}

				return;
			}

			if( (uNextTex0Data[1]&0xe0000000) == 0 ) {

				if( (uCurTex0Data[1]&0x1ff10000) != (uNextTex0Data[1]&0x1ff10000) )
					ZeroGS::Flush(ictx);

				// clut isn't going to be loaded so can ignore, but at least update CSA and CPSM!
				uCurTex0Data[1] = (uCurTex0Data[1]&0xe087ffff)|(uNextTex0Data[1]&0x1f780000);

				if( tex0.cpsm <= 1 ) tex0.csa  =  (uNextTex0Data[1] >> 24) & 0xf;
				else tex0.csa  =  (uNextTex0Data[1] >> 24) & 0x1f;

				tex0.cpsm = (uNextTex0Data[1] >> 19) & 0xe;
				ZeroGS::texClutWrite(ictx);

				bVarsTexSync = FALSE;
				return;
			}

			// fall through
		}
		else {
			//bVarsTexSync = FALSE;
			return;
		}
	}

	ZeroGS::Flush(ictx);
	bVarsTexSync = FALSE;
	bTexConstsSync = FALSE;

	uCurTex0Data[0] = uNextTex0Data[0];
	uCurTex0Data[1] = uNextTex0Data[1];

	tex0.tbp0 =  (uNextTex0Data[0]		& 0x3fff);
	tex0.tbw  = ((uNextTex0Data[0] >> 14) & 0x3f) * 64;
	tex0.psm  =  psm;
	tex0.tw   =  (uNextTex0Data[0] >> 26) & 0xf;
	if (tex0.tw > 10) tex0.tw = 10;
	tex0.tw   = 1<<tex0.tw;
	tex0.th   = ((uNextTex0Data[0] >> 30) & 0x3) | ((uNextTex0Data[1] & 0x3) << 2);
	if (tex0.th > 10) tex0.th = 10;
	tex0.th   = 1<<tex0.th;
	tex0.tcc  =  (uNextTex0Data[1] >>  2) & 0x1;
	tex0.tfx  =  (uNextTex0Data[1] >>  3) & 0x3;

	ZeroGS::fiTexWidth[ictx] = (1/16.0f)/ tex0.tw;
	ZeroGS::fiTexHeight[ictx] = (1/16.0f) / tex0.th;

	if (tex0.tbw == 0) tex0.tbw = 64;

	if( PSMT_ISCLUT(psm) ) {
		tex0.cbp  = ((uNextTex0Data[1] >>  5) & 0x3fff);
		tex0.cpsm =  (uNextTex0Data[1] >> 19) & 0xe;
		tex0.csm  =  (uNextTex0Data[1] >> 23) & 0x1;

		if( tex0.cpsm <= 1 ) tex0.csa  =  (uNextTex0Data[1] >> 24) & 0xf;
		else tex0.csa  =  (uNextTex0Data[1] >> 24) & 0x1f;

		tex0.cld  =  (uNextTex0Data[1] >> 29) & 0x7;

		ZeroGS::texClutWrite(ictx);
	}
}

// does one time only initializing/destruction
class ZeroGSInit
{
public:
	ZeroGSInit() {
		// clear
		g_pbyGSMemory = (u8*)_aligned_malloc(0x00410000, 1024); // leave some room for out of range accesses (saves on the checks)
		memset(g_pbyGSMemory, 0, 0x00410000);

		g_pbyGSClut = (u8*)_aligned_malloc(256*8, 1024); // need 512 alignment!
		memset(g_pbyGSClut, 0, 256*8);
	}
	~ZeroGSInit() {
		_aligned_free(g_pbyGSMemory); g_pbyGSMemory = NULL;
		_aligned_free(g_pbyGSClut); g_pbyGSClut = NULL;
	}
};

static ZeroGSInit s_ZeroGSInit;

HRESULT ZeroGS::Create(LONG _width, LONG _height)
{
	Destroy(1);
	GSStateReset();	

	width = _width;
	height = _height;
	fiRendWidth = 1.0f / width;
	fiRendHeight = 1.0f / height;

	HRESULT hr;

	if( NULL == (pD3D = Direct3DCreate9(D3D_SDK_VERSION)) ) {
		ERROR_LOG(_T("Failed to create the direct3d interface."));
		return E_FAIL;
	}

	D3DDISPLAYMODE d3ddm;
	if( FAILED( hr = pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) {
		ERROR_LOG(_T("Error geting default adapter."));
		return hr;
	}

	if( conf.options & GSOPTION_FULLSCREEN ) {
		// choose best mode
//		RECT rcdesktop;
//		GetWindowRect(GetDesktopWindow(), &rcdesktop);
//		width = rcdesktop.right - rcdesktop.left;
//		height = rcdesktop.bottom - rcdesktop.top;

//		width = height = 0;
//		D3DDISPLAYMODE d3ddmtemp;
//
//		int modes = pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT, d3ddm.Format);
//		for(int i= 0; i < modes; ++i) {
//			pD3D->EnumAdapterModes(D3DADAPTER_DEFAULT, d3ddm.Format, i, &d3ddmtemp);
//
//			if( abs(1024-(int)d3ddmtemp.Width) <= abs(1280-width) && abs(768-(int)d3ddmtemp.Height) <= abs(1024-height) ) {
//				width = d3ddmtemp.Width;
//				height = d3ddmtemp.Height;
//			}
//		}
	}
	else {
		// change to default resolution
		ChangeDisplaySettings(NULL, 0);
	}

	// Set up the structure used to create the D3DDevice. Since we are now
	// using more complex geometry, we will create a device with a zbuffer.
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed = !(conf.options & GSOPTION_FULLSCREEN);
	d3dpp.hDeviceWindow = GShwnd;
	d3dpp.SwapEffect = (conf.options & GSOPTION_FULLSCREEN) ? D3DSWAPEFFECT_FLIP : D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.BackBufferWidth = width;
	d3dpp.BackBufferHeight = height;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//(conf.options & GSOPTION_FULLSCREEN) ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE;
	d3dpp.Flags = DEBUG_PS2 ? D3DPRESENTFLAG_LOCKABLE_BACKBUFFER : 0;

	s_nFullscreen = (conf.options & GSOPTION_FULLSCREEN) ? 1 : 0;

	// Create the D3DDevice
	UINT adapter = D3DADAPTER_DEFAULT;
	D3DDEVTYPE devtype = !DEBUG_PS2 ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF;

#ifndef _DEBUG
	DWORD hwoptions = D3DCREATE_HARDWARE_VERTEXPROCESSING|D3DCREATE_PUREDEVICE;
#else
	DWORD hwoptions = D3DCREATE_HARDWARE_VERTEXPROCESSING;
#endif

#ifndef RELEASE_TO_PUBLIC
	for(UINT i = 0; i < pD3D->GetAdapterCount(); ++i) {
		D3DADAPTER_IDENTIFIER9 id;
		HRESULT hr = pD3D->GetAdapterIdentifier(i, 0, &id);
		if( strcmp(id.Description, "NVIDIA NVPerfHUD") == 0 ) {
			DEBUG_LOG("Using %s adapter\n", id.Description);
			adapter = i;
			devtype = D3DDEVTYPE_REF;
			break;
		}
	}
#endif

	if( FAILED( hr = pD3D->CreateDevice( adapter, devtype, GShwnd,
		!DEBUG_PS2 ? hwoptions : D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pd3dDevice ) ) )
	{	
		ERROR_LOG(_T("Failed to create hardware device, creating software.\n"));

		if( FAILED( hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GShwnd,
									  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
									  &d3dpp, &pd3dDevice ) ) )
		{
			ERROR_LOG(_T("Failed to create software device, switching to reference rasterizer.\n"));

			if( FAILED( hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, GShwnd,
									  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
									  &d3dpp, &pd3dDevice ) ) )
			  return hr;
		}
	}

	// get caps and check if gfx card is ok
	D3DCAPS9 caps;
	pd3dDevice->GetDeviceCaps(&caps);

	if( caps.VertexShaderVersion < D3DVS_VERSION(2,0)  ) {
		ERROR_LOG("*********\nGS ERROR: Need at least vs2.0\n*********\n");
		Destroy(1);
		return E_FAIL;
	}

	conf.mrtdepth = 1;

	if( caps.NumSimultaneousRTs == 1 ) {
		ERROR_LOG("*********\nGS WARNING: Need at least 2 simultaneous render targets. Some zbuffer effects will look wrong\n*********\n");
		conf.mrtdepth = 0;
	}
	if( !(caps.PrimitiveMiscCaps & D3DPMISCCAPS_SEPARATEALPHABLEND) ) {
		ERROR_LOG("*********\nGS ERROR: Need separate alpha blending! Some effects will look bad\n*********\n");
	}
	if( !(caps.PrimitiveMiscCaps & D3DPMISCCAPS_INDEPENDENTWRITEMASKS) ) {
		ERROR_LOG("******\nGS WARNING: Need independent write masks! Some z buffer effects might look bad\n*********\n");
		bIndepWriteMasks = 0;
	}

	if( !(caps.PrimitiveMiscCaps & D3DPMISCCAPS_MRTPOSTPIXELSHADERBLENDING) ) {
		ERROR_LOG("******\nGS WARNING: Need MRT Post Pixel Shader Blending for some effects\n*********\n");
	}

	hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3ddm.Format,
		D3DUSAGE_RENDERTARGET|D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, g_RenderFormat);

	if( g_GameSettings & GAME_32BITTARGS ) {
		g_RenderFormat = D3DFMT_A8R8G8B8;
		ERROR_LOG("Setting 32 bit render target\n");
	}
	else if( FAILED(hr) ) {
		ERROR_LOG("******\nGS ERROR: Device doesn't support alpha blending for 16bit floating point targets.\nQuality will reduce.\n*********\n");
		g_RenderFormat = D3DFMT_A8R8G8B8;
	}	

//	hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3ddm.Format, 0, D3DRTYPE_TEXTURE, D3DFMT_G32R32F);
//
//	if( FAILED(hr) ) {
//		ERROR_LOG("******\nGS ERROR: Device doesn't support G32R32F textures.\nTextures will look bad.\n*********\n");
//	}

	g_MaxTexWidth = caps.MaxTextureWidth;
	g_MaxTexHeight = caps.MaxTextureHeight;
	GPU_TEXWIDTH = caps.MaxTextureWidth/8;
	g_fiGPU_TEXWIDTH = 1.0f / GPU_TEXWIDTH;

	//g_RenderFormat = D3DFMT_A8R8G8B8;

	pd3dDevice->GetRenderTarget(0, &psurfOrgTarg);
	pd3dDevice->GetDepthStencilSurface(&psurfOrgDepth);

	SETRS(D3DRS_ZENABLE, TRUE);
	SETRS(D3DRS_LIGHTING, FALSE);
	SETRS(D3DRS_SPECULARENABLE, FALSE);

	V_RETURN( D3DXCreateFont( pd3dDevice, 15, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET,
							  OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
							  "Arial", &pFont ) );

	// create the vertex decl
	const D3DVERTEXELEMENT9 Decl[] = {
		{ 0, 0,  D3DDECLTYPE_SHORT4N, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
		{ 0, 8, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
		{ 0, 12, D3DDECLTYPE_UBYTE4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
		{ 0, 16, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
		D3DDECL_END() };
	V_RETURN(pd3dDevice->CreateVertexDeclaration(Decl, &pdecl));

#ifdef RELEASE_TO_PUBLIC
	HRSRC hShaderSrc = FindResource(hInst, MAKEINTRESOURCE(IDR_SHADERS), RT_RCDATA);
	assert( hShaderSrc != NULL );
	HGLOBAL hShaderGlob = LoadResource(hInst, hShaderSrc);
	assert( hShaderGlob != NULL );
	s_lpShaderResources = (BYTE*)LockResource(hShaderGlob);
#endif

	// load the effect
	ERROR_LOG("Creating effects\n");
	V_RETURN(LoadEffects());

	g_bDisplayMsg = 0;
	if( caps.VertexShaderVersion >= D3DVS_VERSION(3,0) && caps.PixelShaderVersion >= D3DPS_VERSION(2,0) )
		g_nPixelShaderVer = SHADER_30;
	else if( caps.PixelShaderVersion == D3DPS_VERSION(2,0) )
		g_nPixelShaderVer = SHADER_20;
	else
		g_nPixelShaderVer = SHADER_20a;

#ifdef RELEASE_TO_PUBLIC
	// create a sample shader
	clampInfo temp;
	memset(&temp, 0, sizeof(temp));
	temp.wms = 3; temp.wmt = 3;

	if( g_nPixelShaderVer != SHADER_30 ) {
		// test more
		if( LoadShadeEffect(0, 1, 1, 1, 1, temp, 0) == NULL ) {
			g_nPixelShaderVer = SHADER_20b;
			if( LoadShadeEffect(0, 1, 1, 1, 1, temp, 0) == NULL ) {

				g_nPixelShaderVer = SHADER_20;
				if( LoadShadeEffect(0, 0, 1, 1, 0, temp, 0) == NULL ) {

					ERROR_LOG("*********\nGS ERROR: Need at least ps2.0 (ps2.0a+ recommended)\n*********\n");
					Destroy(1);
					return E_FAIL;
				}
			}
		}
	}
#endif

	// set global shader constants
	pd3dDevice->SetPixelShaderConstantF(27, DXVEC4(0.5f, (g_GameSettings&GAME_EXACTCOLOR)?0.9f/256.0f:0.5f/256.0f, 0,1/255.0f), 1); // g_fExactColor
	pd3dDevice->SetPixelShaderConstantF(28, DXVEC4(-0.7f, -0.65f, 0.9f,0), 1); // g_fBilinear
	pd3dDevice->SetPixelShaderConstantF(29, DXVEC4(1.0f/256.0f, 1.0004f, 1, 0.5f), 1); // g_fZBias
	pd3dDevice->SetPixelShaderConstantF(30, DXVEC4(0,1, 0.001f, 0.5f), 1); // g_fc0
	pd3dDevice->SetPixelShaderConstantF(31, DXVEC4(1/1024.0f, 0.2f/1024.0f, 1/128.0f, 1/512.0f), 1); // g_fMult

	pd3dDevice->SetVertexShaderConstantF(29, DXVEC4(1.0f/256.0f, 1.0004f, 1, 0.5f), 1); // g_fZBias
	pd3dDevice->SetVertexShaderConstantF(30, DXVEC4(0,1, 0.001f, 0.5f), 1); // g_fc0
	pd3dDevice->SetVertexShaderConstantF(31, DXVEC4(0.5f, -0.5f, 0.5f, 0.5f + 0.4f/416.0f), 1); // g_fBitBltTrans

	g_bDisplayMsg = 1;
	if( g_nPixelShaderVer == SHADER_20 )
		conf.bilinear = 0;

	ERROR_LOG("Creating extra effects\n");
	V_RETURN(LoadExtraEffects());

	ERROR_LOG("GS Using pixel shaders %s\n", g_pShaders[g_nPixelShaderVer]);

	pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_STENCIL|D3DCLEAR_ZBUFFER, 0, 1, 0);

	// init draw fns
	drawfn[0] = KickPoint;
	drawfn[1] = KickLine;
	drawfn[2] = KickLine;
	drawfn[3] = KickTriangle;
	drawfn[4] = KickTriangle;
	drawfn[5] = KickTriangleFan;
	drawfn[6] = KickSprite;
	drawfn[7] = KickDummy;

	SetAA(conf.aa);
	GSsetGameCRC(g_LastCRC, g_GameSettings);

	return S_OK;
}

void ZeroGS::Destroy(BOOL bD3D)
{
	DeleteDeviceObjects();

	vb[0].Destroy();
	vb[1].Destroy();

	for(int i = 0; i < ARRAYSIZE(pvs); ++i) {
		SAFE_RELEASE(pvs[i]);
	}
	for(int i = 0; i < ARRAYSIZE(ppsRegular); ++i) {
		SAFE_RELEASE(ppsRegular[i]);
	}
	for(int i = 0; i < ARRAYSIZE(ppsTexture); ++i) {
		SAFE_RELEASE(ppsTexture[i]);
	}

	SAFE_RELEASE(pvsBitBlt);
	SAFE_RELEASE(pvsBitBlt30);
	SAFE_RELEASE(ppsBitBlt[0]); SAFE_RELEASE(ppsBitBlt[1]);
	SAFE_RELEASE(ppsBitBltDepth[0]); SAFE_RELEASE(ppsBitBltDepth[1]);
	SAFE_RELEASE(ppsBitBltDepthTex[0]); SAFE_RELEASE(ppsBitBltDepthTex[1]);
	SAFE_RELEASE(ppsCRTCTarg[0]); SAFE_RELEASE(ppsCRTCTarg[1]);
	SAFE_RELEASE(ppsCRTC[0]); SAFE_RELEASE(ppsCRTC[1]);
	SAFE_RELEASE(ppsCRTC24[0]); SAFE_RELEASE(ppsCRTC24[1]);
	SAFE_RELEASE(ppsOne);

	SAFE_RELEASE(pdecl);
	SAFE_RELEASE(pFont);
	SAFE_RELEASE(psurfOrgTarg);
	SAFE_RELEASE(psurfOrgDepth);

	if( bD3D ) {
		SAFE_RELEASE(pd3dDevice);
		SAFE_RELEASE(pD3D);
	}
}

void ZeroGS::GSStateReset()
{
	icurctx = -1;

	for(int i = 0; i < 2; ++i) {
		LPD3DVB pvb = vb[i].pvb;
		if( pvb != NULL ) pvb->AddRef();
		vb[i].Destroy();
		memset(&vb[i], 0, sizeof(VB));
		
		vb[i].tex0.tw = 1;
		vb[i].tex0.th = 1;
		vb[i].pvb = pvb;
		vb[i].scissor.x1 = 639;
		vb[i].scissor.y1 = 479;
		vb[i].tex0.tbw = 64;
	}

	s_RangeMngr.Clear();
	g_MemTargs.Destroy();
	s_RTs.Destroy();
	s_DepthRTs.Destroy();
	s_BitwiseTextures.Destroy();

	vb[0].ictx = 0;
	vb[1].ictx = 1;
	s_bAlphaSet = FALSE;
}

void ZeroGS::AddMessage(const char* pstr, DWORD ms)
{
	listMsgs.push_back(MESSAGE(pstr, timeGetTime()+ms));
}

void ZeroGS::ChangeWindowSize(int nNewWidth, int nNewHeight)
{
	width = nNewWidth > 16 ? nNewWidth : 16;
	height = nNewHeight > 16 ? nNewHeight : 16;

	if( !(conf.options & GSOPTION_FULLSCREEN) ) {
		conf.width = nNewWidth;
		conf.height = nNewHeight;
		//SaveConfig();
	}
}

void ZeroGS::SetChangeDeviceSize(int nNewWidth, int nNewHeight)
{
	s_nNewWidth = nNewWidth;
	s_nNewHeight = nNewHeight;

	if( !(conf.options & GSOPTION_FULLSCREEN) ) {
		conf.width = nNewWidth;
		conf.height = nNewHeight;
		//SaveConfig();
	}
}

void ZeroGS::Reset()
{
	s_RTs.ResolveAll();
	s_DepthRTs.ResolveAll();
	
	vb[0].Unlock();
	vb[1].Unlock();

	memset(s_nResolveCounts, 0, sizeof(s_nResolveCounts));
	s_nLastResolveReset = 0;

	icurctx = -1;

	GSStateReset();
	Destroy(0);

	drawfn[0] = KickDummy;
	drawfn[1] = KickDummy;
	drawfn[2] = KickDummy;
	drawfn[3] = KickDummy;
	drawfn[4] = KickDummy;
	drawfn[5] = KickDummy;
	drawfn[6] = KickDummy;
	drawfn[7] = KickDummy;
}

void ZeroGS::ChangeDeviceSize(int nNewWidth, int nNewHeight)
{
	int oldscreen = s_nFullscreen;

	int oldwidth = width, oldheight = height;
	if( FAILED(Create(nNewWidth&~7, nNewHeight&~7)) ) {
		DEBUG_LOG("Failed to recreate, changing to old\n");
		if( FAILED(Create(oldwidth, oldheight)) ) {
			MessageBox(NULL, "failed to create dev, exiting...\n", "Error", MB_OK);
			exit(0);
		}
	}

	if( FAILED(InitDeviceObjects()) ) {
		MessageBox(NULL, "failed to init dev objs, exiting...\n", "Error", MB_OK);
		exit(0);
	}

	for(int i = 0; i < 2; ++i) {
		vb[i].bNeedFrameCheck = vb[i].bNeedZCheck = 1;
		vb[i].CheckFrame(0);
	}

	if( oldscreen && !(conf.options & GSOPTION_FULLSCREEN) ) { // if transitioning from full screen
		RECT rc;
		rc.left = 0; rc.top = 0;
		rc.right = conf.width; rc.bottom = conf.height;
		AdjustWindowRect(&rc, conf.winstyle, FALSE);

		RECT rcdesktop;
		GetWindowRect(GetDesktopWindow(), &rcdesktop);

		SetWindowLong( GShwnd, GWL_STYLE, conf.winstyle );
		SetWindowPos(GShwnd, HWND_TOP, ((rcdesktop.right-rcdesktop.left)-(rc.right-rc.left))/2,
			((rcdesktop.bottom-rcdesktop.top)-(rc.bottom-rc.top))/2,
			rc.right-rc.left, rc.bottom-rc.top, SWP_SHOWWINDOW);
		UpdateWindow(GShwnd);
	}

	vb[0].Lock();
	vb[1].Lock();

	assert( vb[0].pbuf != NULL && vb[1].pbuf != NULL );
}

void ZeroGS::SetAA(int mode)
{
	float f;
	
	// need to flush all targets
	s_RTs.ResolveAll();
	s_RTs.Destroy();
	s_DepthRTs.ResolveAll();
	s_DepthRTs.Destroy();

	s_AAx = s_AAy = 0;
	if( mode > 0 ) 
	{
		s_AAx = (mode+1) / 2;
		s_AAy = mode / 2;
	}
	memset(s_nResolveCounts, 0, sizeof(s_nResolveCounts));
	s_nLastResolveReset = 0;

	vb[0].prndr = NULL; vb[0].pdepth = NULL; vb[0].bNeedFrameCheck = 1; vb[0].bNeedZCheck = 1;
	vb[1].prndr = NULL; vb[1].pdepth = NULL; vb[1].bNeedFrameCheck = 1; vb[1].bNeedZCheck = 1;
	
	f = mode > 0 ? 2.0f : 1.0f;
	SETRS(D3DRS_POINTSIZE, FtoDW(f));
}

#ifdef RELEASE_TO_PUBLIC

#define LOAD_VS(Index, ptr) { \
	assert( mapShaderResources.find(Index) != mapShaderResources.end() ); \
	header = mapShaderResources[Index]; \
	assert( (header) != NULL && (header)->index == (Index) ); \
	hr = pd3dDevice->CreateVertexShader((DWORD*)(s_lpShaderResources + (header)->offset), &(ptr)); \
	if( FAILED(hr) || ptr == NULL ) { \
		DEBUG_LOG("errors 0x%x for %d, failed.. try updating your drivers or dx\n", hr, Index); \
		return E_FAIL; \
	} \
} \

#define LOAD_PS(index, ptr) { \
	assert( mapShaderResources.find(index) != mapShaderResources.end() ); \
	header = mapShaderResources[index]; \
	hr = pd3dDevice->CreatePixelShader((DWORD*)(s_lpShaderResources + (header)->offset), &(ptr)); \
	if( FAILED(hr) || ptr == NULL ) { \
		DEBUG_LOG("errors 0x%x for %s, failed.. try updating your drivers or dx\n", hr, index); \
		return E_FAIL; \
	} \
} \

HRESULT ZeroGS::LoadEffects()
{
	assert( s_lpShaderResources != NULL );

	// process the header
	DWORD num = *(DWORD*)s_lpShaderResources;
	SHADERHEADER* header = (SHADERHEADER*)((BYTE*)s_lpShaderResources + 4);

	mapShaderResources.clear();
	while(num-- > 0 ) {
		mapShaderResources[header->index] = header;
		++header;
	}

	// clear the textures
	for(int i = 0; i < ARRAYSIZE(ppsTexture); ++i) {
		SAFE_RELEASE(ppsTexture[i]);
	}
	memset(ppsTexture, 0, sizeof(ppsTexture));

	return S_OK;
}

// called
HRESULT ZeroGS::LoadExtraEffects()
{
	HRESULT hr;

	SHADERHEADER* header;

	DWORD mask = g_nPixelShaderVer == SHADER_30 ? SH_30 : 0;

	const int vsshaders[4] = { SH_REGULARVS, SH_TEXTUREVS, SH_REGULARFOGVS, SH_TEXTUREFOGVS };
	for(int i = 0; i < 4; ++i) {
		LOAD_VS(vsshaders[i]|mask, pvs[2*i]);
		LOAD_VS(vsshaders[i]|mask|SH_CONTEXT1, pvs[2*i+1]);
		LOAD_VS(vsshaders[i]|mask|SH_WRITEDEPTH, pvs[2*i+8]);
		LOAD_VS(vsshaders[i]|mask|SH_WRITEDEPTH|SH_CONTEXT1, pvs[2*i+8+1]);
	}
	
	LOAD_VS(SH_BITBLTVS, pvsBitBlt);
	//LOAD_VS(SH_BITBLTVS|SH_30, pvsBitBlt30);

	LOAD_PS(SH_REGULARPS|mask, ppsRegular[0]);
	LOAD_PS(SH_REGULARFOGPS|mask, ppsRegular[1]);
	LOAD_PS(SH_REGULARPS|SH_WRITEDEPTH|mask, ppsRegular[2]);
	LOAD_PS(SH_REGULARFOGPS|SH_WRITEDEPTH|mask, ppsRegular[3]);

	LOAD_PS(SH_BITBLTPS, ppsBitBlt[0]); LOAD_PS(SH_BITBLTAAPS, ppsBitBlt[0]);
	LOAD_PS(SH_BITBLTDEPTHPS, ppsBitBltDepth[0]); LOAD_PS(SH_BITBLTDEPTHMRTPS, ppsBitBltDepth[1]);
	LOAD_PS(SH_BITBLTDEPTHTEXPS, ppsBitBltDepthTex[0]); LOAD_PS(SH_BITBLTDEPTHTEXMRTPS, ppsBitBltDepthTex[1]);
	LOAD_PS(SH_CRTCTARGPS, ppsCRTCTarg[0]); LOAD_PS(SH_CRTCTARGINTERPS, ppsCRTCTarg[1]);
	LOAD_PS(SH_CRTCPS, ppsCRTC[0]); LOAD_PS(SH_CRTCINTERPS, ppsCRTC[1]);
	LOAD_PS(SH_CRTC24PS, ppsCRTC24[0]); LOAD_PS(SH_CRTC24INTERPS, ppsCRTC24[1]);
	LOAD_PS(SH_ZEROPS|mask, ppsOne);
	LOAD_PS(SH_BASETEXTUREPS, ppsBaseTexture);
	LOAD_PS(SH_CONVERT16TO32PS, ppsConvert16to32);
	LOAD_PS(SH_CONVERT32TO16PS, ppsConvert32to16);

	return S_OK;
}

LPD3DPS ZeroGS::LoadShadeEffect(int type, int texfilter, int fog, int testaem, int exactcolor, const clampInfo& clamp, int context)
{
	int texwrap;
	assert( texfilter < NUM_FILTERS );

	if(g_nPixelShaderVer == SHADER_20 )
		texfilter = 0;
	if(g_nPixelShaderVer == SHADER_20 )
		exactcolor = 0;

	if( clamp.wms == clamp.wmt ) {
		switch( clamp.wms ) {
			case 0: texwrap = TEXWRAP_REPEAT; break;
			case 1: texwrap = TEXWRAP_CLAMP; break;
			case 2: texwrap = TEXWRAP_CLAMP; break;
			default: texwrap = TEXWRAP_REGION_REPEAT; break;
		}
	}
	else if( clamp.wms==3||clamp.wmt==3)
		texwrap = TEXWRAP_REGION_REPEAT;
	else
		texwrap = TEXWRAP_REPEAT_CLAMP;

	int index = GET_SHADER_INDEX(type, texfilter, texwrap, fog, s_bWriteDepth, testaem, exactcolor, context, 0);

	assert( index < ARRAYSIZE(ppsTexture) );
	LPD3DPS* pps = ppsTexture+index;

	if( *pps != NULL ) 
		return *pps;

	index += NUM_SHADERS*g_nPixelShaderVer;
	assert( mapShaderResources.find(index) != mapShaderResources.end() );
	SHADERHEADER* header = mapShaderResources[index];
	if( header == NULL ) DEBUG_LOG("%d %d\n", index%NUM_SHADERS, g_nPixelShaderVer);
	assert( header != NULL );
	HRESULT hr = pd3dDevice->CreatePixelShader((DWORD*)(s_lpShaderResources + header->offset), pps);

	if( SUCCEEDED(hr) )
		return *pps;

	if( g_bDisplayMsg )
		ERROR_LOG("Failed to create shader %d,%d,%d,%d\n", 3, fog, texfilter, 4*clamp.wms+clamp.wmt);

	return NULL;
}
	
#else // not RELEASE_TO_PUBLIC

//#define EFFECT_NAME "f:\\ps2dev\\pcsx2\\zerogs\\dx\\"
#define EFFECT_NAME ".\\"
#define COMPILE_SHADER(name, type, flags) 

class ZeroGSShaderInclude : public ID3DXInclude
{
public:
	int context;
	STDMETHOD(Open)(D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes)
	{
		const char* pfilename = pFileName;
		char strfile[255];
		if( strstr(pFileName, "ps2hw_ctx") != NULL ) {

			_snprintf(strfile, 255, "%sps2hw_ctx%d.fx", EFFECT_NAME, context);
			pfilename = strfile;
		}
		else if( strstr(pFileName, "\\") == NULL ) {
			_snprintf(strfile, 255, "%s%s", EFFECT_NAME, pFileName);
			pfilename = strfile;
		}
		
		FILE* f = fopen(pfilename, "rb");

		if( f == NULL )
			return E_FAIL;

		fseek(f, 0, SEEK_END);
		DWORD size = ftell(f);
		fseek(f, 0, SEEK_SET);
		char* buffer = new char[size+1];
		fread(buffer, size, 1, f);
		buffer[size] = 0;

		*ppData = buffer;
		*pBytes = size;
		fclose(f);

		return S_OK;
	}

	STDMETHOD(Close)(LPCVOID pData)
	{
		delete[] (char*)pData;
		return S_OK;
	}
};

#define LOAD_VS(name, ptr, shaderver) { \
	LPD3DXBUFFER pShader, pError; \
	V(D3DXCompileShaderFromFile(EFFECT_NAME"ps2hw.fx", pmacros, pInclude, name, shaderver, ShaderFlagsVS, &pShader, &pError, NULL)); \
	if( FAILED(hr) ) \
	{ \
		DEBUG_LOG("Failed to load vs %s: \n%s\n", name, pError->GetBufferPointer()); \
		SAFE_RELEASE(pShader); \
		SAFE_RELEASE(pError); \
		return hr; \
	} \
	hr = pd3dDevice->CreateVertexShader((const DWORD*)pShader->GetBufferPointer(), &(ptr)); \
	SAFE_RELEASE(pShader); \
	SAFE_RELEASE(pError); \
} \

#define LOAD_PS(name, ptr, shmodel) { \
	LPD3DXBUFFER pShader, pError; \
	SAFE_RELEASE(ptr); \
	V(D3DXCompileShaderFromFile(EFFECT_NAME"ps2hw.fx", pmacros, pInclude, name, shmodel, ShaderFlagsPS, &pShader, &pError, NULL)); \
	if( FAILED(hr) ) \
	{ \
		DEBUG_LOG("Failed to load ps %s: \n%s\n", name, pError->GetBufferPointer()); \
		SAFE_RELEASE(pShader); \
		SAFE_RELEASE(pError); \
		return hr; \
	} \
	hr = pd3dDevice->CreatePixelShader((const DWORD*)pShader->GetBufferPointer(), &(ptr)); \
	SAFE_RELEASE(pShader); \
	SAFE_RELEASE(pError); \
	if( FAILED(hr) || ptr == NULL ) { \
		DEBUG_LOG("errors 0x%x for %s, failed.. try updating your drivers or dx\n", hr, name); \
		return E_FAIL; \
	} \
} \

HRESULT ZeroGS::LoadEffects()
{
	// clear the textures
	for(int i = 0; i < ARRAYSIZE(ppsTexture); ++i) {
		SAFE_RELEASE(ppsTexture[i]);
	}
	memset(ppsTexture, 0, sizeof(ppsTexture));

	return S_OK;
}

#define VS_VER (g_nPixelShaderVer == SHADER_20?"vs_2_0":"vs_3_0")
#define PS_VER (g_nPixelShaderVer == SHADER_20?"ps_2_0":"ps_3_0")

HRESULT ZeroGS::LoadExtraEffects()
{
	HRESULT hr;
	DWORD ShaderFlagsPS = !DEBUG_PS2 ? 0 : (D3DXSHADER_DEBUG|D3DXSHADER_SKIPOPTIMIZATION);
	DWORD ShaderFlagsVS = !DEBUG_PS2 ? 0 : (D3DXSHADER_DEBUG|D3DXSHADER_SKIPOPTIMIZATION);

	ZeroGSShaderInclude inc;
	inc.context = 0;
	ZeroGSShaderInclude* pInclude = &inc;

	//assert( g_nPixelShaderVer == SHADER_30) ;
	const char* pstrps = g_nPixelShaderVer == SHADER_20 ? "ps_2_0" : "ps_2_a";
	const char* pvsshaders[4] = { "RegularVS", "TextureVS", "RegularFogVS", "TextureFogVS" };

	D3DXMACRO macros[2] = {0};
	D3DXMACRO* pmacros = NULL;

	macros[0].Name = "WRITE_DEPTH";
	macros[0].Definition = "1";	

	for(int i = 0; i < 4; ++i) {
		pmacros = NULL;
		inc.context = 0;
		LOAD_VS(pvsshaders[i], pvs[2*i], VS_VER);
		inc.context = 1;
		LOAD_VS(pvsshaders[i], pvs[2*i+1], VS_VER);

		pmacros = macros;
		inc.context = 0;
		LOAD_VS(pvsshaders[i], pvs[2*i+8], VS_VER);
		inc.context = 1;
		LOAD_VS(pvsshaders[i], pvs[2*i+8+1], VS_VER);
	}

	inc.context = 0;
	pmacros = NULL;
	LOAD_PS("RegularPS", ppsRegular[0], PS_VER);
	LOAD_PS("RegularFogPS", ppsRegular[1], PS_VER);

	pmacros = macros;
	LOAD_PS("RegularPS", ppsRegular[2], PS_VER);
	LOAD_PS("RegularFogPS", ppsRegular[3], PS_VER);

	pmacros = NULL;
	LOAD_VS("BitBltVS", pvsBitBlt, "vs_2_0");
	LOAD_PS("BitBltPS", ppsBitBlt[0], pstrps);
	LOAD_PS("BitBltAAPS", ppsBitBlt[1], pstrps);
	LOAD_PS("BitBltDepthPS", ppsBitBltDepth[0], pstrps);
	LOAD_PS("BitBltDepthMRTPS", ppsBitBltDepth[1], pstrps);
	LOAD_PS("BitBltDepthTexPS", ppsBitBltDepthTex[0], pstrps);
	LOAD_PS("BitBltDepthTexMRTPS", ppsBitBltDepthTex[1], pstrps);
	LOAD_PS("CRTCTargPS", ppsCRTCTarg[0], pstrps); LOAD_PS("CRTCTargInterPS", ppsCRTCTarg[1], pstrps);
	LOAD_PS("CRTCPS", ppsCRTC[0], pstrps); LOAD_PS("CRTCInterPS", ppsCRTC[1], pstrps);
	LOAD_PS("CRTC24PS", ppsCRTC24[0], pstrps); LOAD_PS("CRTC24InterPS", ppsCRTC24[1], pstrps);
	LOAD_PS("ZeroPS", ppsOne, PS_VER);
	LOAD_PS("BaseTexturePS", ppsBaseTexture, pstrps);
	LOAD_PS("Convert16to32PS", ppsConvert16to32, pstrps);
	LOAD_PS("Convert32to16PS", ppsConvert32to16, pstrps);

	return S_OK;
}

LPD3DPS ZeroGS::LoadShadeEffect(int type, int texfilter, int fog, int testaem, int exactcolor, const clampInfo& clamp, int context)
{
	int texwrap;
	
	assert( texfilter < NUM_FILTERS );
	//assert( g_nPixelShaderVer == SHADER_30 );
	if( clamp.wms == clamp.wmt ) {
		switch( clamp.wms ) {
			case 0: texwrap = TEXWRAP_REPEAT; break;
			case 1: texwrap = TEXWRAP_CLAMP; break;
			case 2: texwrap = TEXWRAP_CLAMP; break;
			default:
				texwrap = TEXWRAP_REGION_REPEAT; break;
		}
	}
	else if( clamp.wms==3||clamp.wmt==3)
		texwrap = TEXWRAP_REGION_REPEAT;
	else
		texwrap = TEXWRAP_REPEAT_CLAMP;

	int index = GET_SHADER_INDEX(type, texfilter, texwrap, fog, s_bWriteDepth, testaem, exactcolor, context, 0);

	LPD3DPS* pps = ppsTexture+index;

	if( *pps != NULL ) 
		return *pps;
	
	ZeroGSShaderInclude inc;
	inc.context = context;

	HRESULT hr = LoadShaderFromType(EFFECT_NAME"ps2hw.fx", type, texfilter, texwrap, fog, s_bWriteDepth, testaem, exactcolor, g_nPixelShaderVer, 0, pd3dDevice, &inc, pps);

	if( SUCCEEDED(hr) )
		return *pps;

	DEBUG_LOG("Failed to create shader %d,%d,%d,%d\n", type, fog, texfilter, 4*clamp.wms+clamp.wmt);

	return NULL;
}

#endif // RELEASE_TO_PUBLIC

HRESULT ZeroGS::InitDeviceObjects()
{
	//g_GameSettings |= 0;//GAME_VSSHACK|GAME_FULL16BITRES|GAME_NODEPTHRESOLVE|GAME_FASTUPDATE;
	//s_bWriteDepth = TRUE;
	DeleteDeviceObjects();

	int i;
	HRESULT hr;
	SETRS(D3DRS_SRCBLEND, D3DBLEND_ONE);
	SETRS(D3DRS_DESTBLEND, D3DBLEND_ONE);

	if( pFont ) V_RETURN( pFont->OnResetDevice() );
	V_RETURN( D3DXCreateSprite( pd3dDevice, &pSprite ) );

	V(D3DXCreateTextureFromResource(pd3dDevice, hInst, MAKEINTRESOURCE( IDB_ZEROGSLOGO ), &ptexLogo));

	for(i = 0; i < 2; ++i)
	{
		V_RETURN(pd3dDevice->CreateVertexBuffer( sizeof(VertexGPU) * POINT_BUFFERSIZE, D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &vb[i].pvb, NULL));
	}

	// create the blocks texture
	D3DFORMAT blockfmt = D3DFMT_R32F;
	g_fBlockMult = 1;

	if( FAILED(hr = pd3dDevice->CreateTexture(BLOCK_TEXWIDTH, BLOCK_TEXHEIGHT, 1, 0, blockfmt, D3DPOOL_MANAGED, &ptexBlocks, NULL)) ) {
		blockfmt = D3DFMT_G16R16;
		g_fBlockMult = 65535.0f*(float)g_fiGPU_TEXWIDTH;
		V_RETURN(pd3dDevice->CreateTexture(BLOCK_TEXWIDTH, BLOCK_TEXHEIGHT, 1, 0, blockfmt, D3DPOOL_MANAGED, &ptexBlocks, NULL));
	}

	if( blockfmt == D3DFMT_R32F ) {
		if( FAILED(hr = pd3dDevice->CreateTexture(BLOCK_TEXWIDTH, BLOCK_TEXHEIGHT, 1, 0, D3DFMT_A32B32G32R32F, D3DPOOL_MANAGED, &ptexBilinearBlocks, NULL)) ) {
			DEBUG_LOG("Failed to create bilinear block texture, fmt = D3DFMT_A32B32G32R32F\n");
		}
	}
	else ptexBilinearBlocks = NULL;

	// fill a simple rect
	V_RETURN(pd3dDevice->CreateVertexBuffer( 4 * sizeof(VertexGPU), D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &pvbRect, NULL));
	VertexGPU* pvert;

	pvbRect->Lock(0, 0, (void**)&pvert, 0);
	pvert->x = -0x7fff; pvert->y = 0x7fff; pvert->z = 0; pvert->s = 0; pvert->t = 0; pvert++;
	pvert->x = 0x7fff; pvert->y = 0x7fff; pvert->z = 0; pvert->s = 1; pvert->t = 0; pvert++;
	pvert->x = -0x7fff; pvert->y = -0x7fff; pvert->z = 0; pvert->s = 0; pvert->t = 1; pvert++;
	pvert->x = 0x7fff; pvert->y = -0x7fff; pvert->z = 0; pvert->s = 1; pvert->t = 1; pvert++;
	pvbRect->Unlock();

	D3DLOCKED_RECT lock, lockbilinear;
	ptexBlocks->LockRect(0, &lock, NULL, 0);

	if( ptexBilinearBlocks != NULL )
		ptexBilinearBlocks->LockRect(0, &lockbilinear, NULL, 0);
	
	BLOCK::FillBlocks(&lock, ptexBilinearBlocks != NULL ? &lockbilinear : NULL, blockfmt);

	ptexBlocks->UnlockRect(0);
	if( ptexBilinearBlocks != NULL )
		ptexBilinearBlocks->UnlockRect(0);

	// create the conversion textures
	V_RETURN(pd3dDevice->CreateTexture(256, 256, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &ptexConv16to32, NULL));
	ptexConv16to32->LockRect(0, &lock, NULL, 0);
	assert(lock.Pitch == 256*4);
	u32* dst = (u32*)lock.pBits;
	for(i = 0; i < 256*256; ++i) {
		DWORD tempcol = RGBA16to32(i);
		// have to flip r and b
		*dst++ = (tempcol&0xff00ff00)|((tempcol&0xff)<<16)|((tempcol&0xff0000)>>16);
	}
	ptexConv16to32->UnlockRect(0);

	V_RETURN(pd3dDevice->CreateVolumeTexture(32, 32, 32, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &ptexConv32to16, NULL));
	D3DLOCKED_BOX lockbox;
	ptexConv32to16->LockBox(0, &lockbox, NULL, 0);
	dst = (u32*)lockbox.pBits;
	for(i = 0; i < 32; ++i) {
		for(int j = 0; j < 32; ++j) {
			for(int k = 0; k < 32; ++k) {
				u32 col = (i<<10)|(j<<5)|k;
				*dst++ = ((col&0xff)<<16)|(col&0xff00);
			}
		}
	}
	ptexConv32to16->UnlockBox(0);

	// set samplers
	for(i = 0; i < 8; ++i) {
		pd3dDevice->SetSamplerState(i, D3DSAMP_MINFILTER, D3DTEXF_POINT);
		pd3dDevice->SetSamplerState(i, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		pd3dDevice->SetSamplerState(i, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
		pd3dDevice->SetSamplerState(i, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
	}

	//pd3dDevice->SetSamplerState(SAMP_SRC, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	//pd3dDevice->SetSamplerState(SAMP_SRC, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BLOCKS, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BLOCKS, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSW, D3DTADDRESS_CLAMP); // can be used as a 3d texture
	pd3dDevice->SetSamplerState(SAMP_BITWISEANDX, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BITWISEANDX, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BITWISEANDY, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	pd3dDevice->SetSamplerState(SAMP_BITWISEANDY, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);

	pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

	pd3dDevice->SetTexture(SAMP_BLOCKS, ptexBlocks);
	pd3dDevice->SetTexture(SAMP_BILINEARBLOCKS, ptexBilinearBlocks);
	pd3dDevice->SetVertexDeclaration(pdecl);
	
	SETRS(D3DRS_STENCILENABLE, FALSE);
	SETRS(D3DRS_SCISSORTESTENABLE, 1);
	SETRS(D3DRS_SEPARATEALPHABLENDENABLE, USEALPHABLENDING);
	SETRS(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
	SETRS(D3DRS_DESTBLENDALPHA, D3DBLEND_ZERO);
	SETRS(D3DRS_CULLMODE, D3DCULL_NONE);
	SETRS(D3DRS_BLENDFACTOR, 0x80000000);
	SETRS(D3DRS_COLORWRITEENABLE1, 0);

	// points
	SETRS(D3DRS_POINTSCALEENABLE, FALSE);
	SETRS(D3DRS_POINTSIZE, FtoDW(1.0f));

	g_nDepthBias = 0;
	SETRS(D3DRS_DEPTHBIAS, FtoDW(0.000015f));

	SETCONSTF(GPU_Z, g_vdepth);//vb[icurctx].zbuf.psm&3]);

	s_vznorm = DXVEC4(g_filog32, 0, 0,0);
	SETCONSTF(GPU_ZNORM, s_vznorm);

	return S_OK;
}

void ZeroGS::DeleteDeviceObjects()
{
	if( s_aviinit ) {
		StopCapture();
		STOP_AVI();
		DEBUG_LOG("zerogs.avi stopped");
		s_aviinit = 0;
	}

	SAFE_RELEASE(s_ptexAVICapture);

	if( pFont ) pFont->OnLostDevice();
	SAFE_RELEASE(pSprite);

	g_MemTargs.Destroy();
	s_RTs.Destroy();
	s_DepthRTs.Destroy();
	s_BitwiseTextures.Destroy();

	SAFE_RELEASE(s_ptexInterlace);
	SAFE_RELEASE(pvbRect);
	SAFE_RELEASE(ptexBlocks);
	SAFE_RELEASE(ptexBilinearBlocks);
	SAFE_RELEASE(ptexConv16to32);
	SAFE_RELEASE(ptexConv32to16);
	s_bAlphaSet = FALSE;

	vb[0].Unlock();
	SAFE_RELEASE(vb[0].pvb);

	vb[1].Unlock();
	SAFE_RELEASE(vb[1].pvb);
}

void ZeroGS::Prim()
{
	if( g_bIsLost )
		return;

	VB& curvb = vb[prim->ctxt];
	if( curvb.CheckPrim() )
		Flush(prim->ctxt);
	curvb.curprim._val = prim->_val;

	// flush the other pipe if sharing the same buffer
//	if( vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp && vb[!prim->ctxt].dwCount > 0 )
//	{
//		assert( vb[prim->ctxt].dwCount == 0 );
//		Flush(!prim->ctxt);
//	}

	curvb.curprim.prim = prim->prim;
	vb[prim->ctxt].Lock();
}

int GetTexFilter(const tex1Info& tex1)
{
	// always force
	if( conf.bilinear == 2 )
		return 1;

	int texfilter = 0;
	if( conf.bilinear && ptexBilinearBlocks != NULL ) {
		if( tex1.mmin <= 1 )
			texfilter = tex1.mmin|tex1.mmag;
		else
			texfilter = tex1.mmag ? ((tex1.mmin+2)&5) : tex1.mmin;

		texfilter = texfilter == 1 || texfilter == 4 || texfilter == 5;
	}
	return texfilter;
}

void ZeroGS::ReloadEffects()
{
#ifndef RELEASE_TO_PUBLIC
	for(int i = 0; i < ARRAYSIZE(ppsTexture); ++i) {
		SAFE_RELEASE(ppsTexture[i]);
	}
	memset(ppsTexture, 0, sizeof(ppsTexture));
	LoadExtraEffects();
#endif
}

static int s_ClutResolve = 0;
static int s_PSM8Resolve = 0;
void ZeroGS::Flush(int context)
{
	assert( context >= 0 && context <= 1 );

#ifndef RELEASE_TO_PUBLIC
	if( g_bUpdateEffect ) {
		ReloadEffects();
		g_bUpdateEffect = 0;
	}
#endif

	VB& curvb = vb[context];
	const pixTest curtest = curvb.test;
	if( curvb.dwCount == 0 || (curtest.zte && curtest.ztst == 0) || g_bIsLost ) {
		curvb.dwCount = 0;
		return;
	}

	if( s_RangeMngr.ranges.size() > 0 ) {

		// don't want infinite loop
		DWORD prevcount = curvb.dwCount;
		curvb.dwCount = 0;
		FlushTransferRanges(curvb.curprim.tme ? &curvb.tex0 : NULL);

		curvb.dwCount = prevcount;
		//if( curvb.dwCount == 0 )
		//	return;
	}

	if( curvb.bNeedTexCheck ) {
		curvb.FlushTexData();

		if( curvb.dwCount == 0 )
			return;
	}

	if( !s_bBeginScene ) {
		pd3dDevice->BeginScene();
		s_bBeginScene = TRUE;
	}

	curvb.Unlock();

	LPD3DTEX ptexRenderTargetCached = NULL;
	int cachedtbp0, cachedtbw, cachedtbh;

	//s_bWriteDepth = TRUE;

	//static int lasttime = 0;
	//fprintf(gsLog, "%d: %d\n", g_SaveFrameNum, timeGetTime()-lasttime);
	//lasttime = timeGetTime();

	if( curvb.bNeedFrameCheck || curvb.bNeedZCheck ) {

		int tpsm = curvb.tex0.psm;
		if( curvb.bNeedTexCheck )
			tpsm = (curvb.uNextTex0Data[0] >> 20) & 0x3f;

		if( tpsm == PSMT8H && (g_GameSettings&GAME_NOTARGETCLUT) ) {
			curvb.dwCount = 0;
			return;
		}

		// check for the texture before checking the frame (since things could get destroyed)
		if( (g_GameSettings&GAME_PARTIALPOINTERS) &&curvb.curprim.tme  ) {

//			if( (curvb.gsfb.fbp&0xff) != 0 ) {
//				curvb.dwCount = 0;
//				return;
//			}
		
			// if texture is part of a previous target, use that instead
			int tbw = curvb.tex0.tbw;
			int tbp0 = curvb.tex0.tbp0;

			if( curvb.bNeedTexCheck ) {
				// not yet initied, but still need to get correct target! (xeno3 ingame)
				tbp0 =  (curvb.uNextTex0Data[0]		& 0x3fff);
				tbw  = ((curvb.uNextTex0Data[0] >> 14) & 0x3f) * 64;
			}

			if( (tpsm&~1) == 0 ) {
				CRenderTarget* ptemptarg = s_RTs.GetTarg(tbp0, tbw);
				if( ptemptarg != NULL && (ptemptarg->psm&~1) == (tpsm&~1) ) {
					ptexRenderTargetCached = ptemptarg->ptex;
					ptexRenderTargetCached->AddRef();
					cachedtbp0 = ptemptarg->fbp;
					cachedtbw = ptemptarg->fbw;
					cachedtbh = ptemptarg->fbh;
				}
			}
		}
		curvb.CheckFrame(curvb.curprim.tme ? curvb.tex0.tbp0 : 0);
	}

//	if( g_SaveFrameNum == 976 ) {
//		curvb.prndr->ConvertTo32();
//	}
	if( curvb.prndr == NULL || curvb.pdepth == NULL ) {
		WARN_LOG("Current render target NULL (ctx: %d)", context);
		curvb.dwCount = 0;
		SAFE_RELEASE(ptexRenderTargetCached);
		return;
	}

#if defined(PRIM_LOG) && defined(_DEBUG)
	static const char* patst[8] = { "NEVER", "ALWAYS", "LESS", "LEQUAL", "EQUAL", "GEQUAL", "GREATER", "NOTEQUAL"};
	static const char* pztst[4] = { "NEVER", "ALWAYS", "GEQUAL", "GREATER" };
	static const char* pafail[4] = { "KEEP", "FB_ONLY", "ZB_ONLY", "RGB_ONLY" };
	PRIM_LOG("**Drawing ctx %d, num %d, fbp: 0x%x, zbp: 0x%x, fpsm: %d, zpsm: %d, fbw: %d\n", context, vb[context].dwCount, curvb.prndr->fbp, curvb.zbuf.zbp, curvb.prndr->psm, curvb.zbuf.psm, curvb.prndr->fbw);
	PRIM_LOG("prim: prim=%x iip=%x tme=%x fge=%x abe=%x aa1=%x fst=%x ctxt=%x fix=%x\n",
			 curvb.curprim.prim, curvb.curprim.iip, curvb.curprim.tme, curvb.curprim.fge, curvb.curprim.abe, curvb.curprim.aa1, curvb.curprim.fst, curvb.curprim.ctxt, curvb.curprim.fix);
	PRIM_LOG("test: ate:%d, atst: %s, aref: %d, afail: %s, date: %d, datm: %d, zte: %d, ztst: %s, fba: %d\n",
		curvb.test.ate, patst[curvb.test.atst], curvb.test.aref, pafail[curvb.test.afail], curvb.test.date, curvb.test.datm, curvb.test.zte, pztst[curvb.test.ztst], curvb.fba.fba);
	PRIM_LOG("alpha: A%d B%d C%d D%d FIX:%d pabe: %d; aem: %d, ta0: %d, ta1: %d\n", curvb.alpha.a, curvb.alpha.b, curvb.alpha.c, curvb.alpha.d, curvb.alpha.fix, gs.pabe, gs.texa.aem, gs.texa.ta[0], gs.texa.ta[1]);
	PRIM_LOG("tex0: tbp0=0x%x, tbw=%d, psm=0x%x, tw=%d, th=%d, tcc=%d, tfx=%d, cbp=0x%x, cpsm=0x%x, csm=%d, csa=%d, cld=%d\n", 
			 curvb.tex0.tbp0, curvb.tex0.tbw, curvb.tex0.psm, curvb.tex0.tw, 
			 curvb.tex0.th, curvb.tex0.tcc, curvb.tex0.tfx, curvb.tex0.cbp,
		 	 curvb.tex0.cpsm, curvb.tex0.csm, curvb.tex0.csa, curvb.tex0.cld);
	PRIM_LOG("frame: %d\n\n", g_SaveFrameNum);
#endif
	
	CMemoryTarget* pmemtarg = NULL;
	CRenderTarget* ptextarg = NULL;

	// kh2 hack
//	if( curvb.dwCount == 2 && curvb.curprim.tme == 0 && curvb.curprim.abe == 0 && (curvb.tex0.tbp0 == 0x2a00 || curvb.tex0.tbp0==0x1d00) ) {
//		// skip
//		DEBUG_LOG("skipping\n");
//		g_SaveFrameNum++;
//		curvb.dwCount = 0;
//		return;
//	}

	if( curtest.date || gs.pabe )
		SetDestAlphaTest();

	// set the correct pixel shaders
	if( curvb.curprim.tme && ptexRenderTargetCached == NULL ) {

		// if texture is part of a previous target, use that instead
		int tbw = curvb.tex0.tbw;
		int tbp0 = curvb.tex0.tbp0;
		int tpsm = curvb.tex0.psm;

		if( curvb.bNeedTexCheck ) {
			// not yet initied, but still need to get correct target! (xeno3 ingame)
			tbp0 =  (curvb.uNextTex0Data[0]		& 0x3fff);
			tbw  = ((curvb.uNextTex0Data[0] >> 14) & 0x3f) * 64;
			tpsm = (curvb.uNextTex0Data[0] >> 20) & 0x3f;
		}
		ptextarg = s_RTs.GetTarg(tbp0, tbw);

		if( ptextarg == NULL && tpsm == PSMT8 ) {
			// check for targets with half the width
			ptextarg = s_RTs.GetTarg(tbp0, tbw/2);
			if( ptextarg == NULL ) {
				tbp0 &= ~0x7ff;
				ptextarg = s_RTs.GetTarg(tbp0, tbw/2); // mgs3 hack

				if( ptextarg == NULL ) {
					// check the next level (mgs3)
					tbp0 &= ~0xfff;
					ptextarg = s_RTs.GetTarg(tbp0, tbw/2); // mgs3 hack
				}

				if( ptextarg != NULL && ptextarg->start > tbp0*256 ) {
					// target beyond range, so ignore
					ptextarg = NULL;
				}
			}

//			if( ptextarg != NULL ) {
//				// make sure target isn't invalidated by the ranges
//				for(vector<CRangeManager::RANGE>::iterator itrange = s_RangeMngr.ranges.begin(); itrange != s_RangeMngr.ranges.end(); ++itrange ) {
//
//					int start = itrange->start;
//					int end = itrange->end;
//
//					// if start and end are in the range or there's a range that is between tbp0 and start, then remove
//					if( (start <= tbp0*256 && end > tbp0*256) || (start >= ptextarg->fbp*256 && start <= tbp0*256) ) {
//						ptextarg = NULL;
//						break;
//					}
//				}
//			}

			if( ptextarg != NULL && !(ptextarg->status&CRenderTarget::TS_NeedUpdate) ) {
				// find the equivalent memtarg
				if( s_PSM8Resolve == 0 ) { //|| (s_PSM8Resolve > 0 && s_PSM8Resolve+128 < g_SaveFrameNum) ) {
					DWORD prevcount = curvb.dwCount;
					curvb.dwCount = 0;
					if( ptextarg->pmimicparent != NULL )
						ptextarg->pmimicparent->Resolve();
					else
						ptextarg->Resolve();
					curvb.dwCount = prevcount;
					s_PSM8Resolve = g_SaveFrameNum; // stop from resolving again (once per frame)
				}

				tex0Info mytex0 = curvb.tex0;
				mytex0.tbp0 = tbp0;
				if( ptextarg->pmimicparent != NULL ) {
					mytex0.tbp0 = ptextarg->pmimicparent->fbp;
				}
				pmemtarg = g_MemTargs.GetMemoryTarget(mytex0, 1);

				// have to add an offset to all texture reads
				mytex0.tbp0 = tbp0; // change so that SetTexVariablesInt can set the right offsets

				SetTexVariablesInt(context, GetTexFilter(curvb.tex1), mytex0, pmemtarg, s_bForceTexFlush);	
				curvb.bVarsTexSync = TRUE;

				ptextarg = NULL; // won't be needing this anymore
			}
		}

		if( (tpsm&0x30)==0x30 && ptextarg == NULL ) {
			// try depth
			ptextarg = s_DepthRTs.GetTarg(tbp0, tbw);
		}

		if( ptextarg == NULL && (g_GameSettings&GAME_TEXTURETARGS) ) {
			// check if any part of the texture intersects the current target
			if( !PSMT_ISCLUT(tpsm) && curvb.tex0.tbp0 >= curvb.frame.fbp && (curvb.tex0.tbp0 << 8) < curvb.prndr->end) {
				ptextarg = curvb.prndr;
			}
		}

		if( ptextarg != NULL && !(ptextarg->status&CRenderTarget::TS_NeedUpdate) ) {
			if( PSMT_ISCLUT(tpsm) && tpsm != PSMT8H && tpsm != PSMT8 ) { // handle 8h cluts
				// don't support clut targets, read from mem
				// 4hl - kh2 check
				if( tpsm != PSMT4HL && tpsm != PSMT4HH && s_ClutResolve <= 1 ) { // xenosaga requires 2 resolves
					DWORD prevcount = curvb.dwCount;
					curvb.dwCount = 0;
					ptextarg->Resolve();
					s_ClutResolve++;
					curvb.dwCount = prevcount;
				}
				ptextarg = NULL;
			}
			else {
				if( ptextarg == curvb.prndr ) {
					// need feedback
					if( ptextarg->pmimicparent != NULL ) {
						// if the target is mimic, create the feedback of the parent
						assert( ptextarg->pmimicparent->ptex == ptextarg->ptex || ptextarg->pmimicparent->ptexFeedback == ptextarg->ptex );
						SAFE_RELEASE(ptextarg->ptexFeedback);
						SAFE_RELEASE(ptextarg->psurfFeedback);
						ptextarg->pmimicparent->CreateFeedback();
						ptextarg->ptex = ptextarg->pmimicparent->ptex;
						ptextarg->ptexFeedback = ptextarg->pmimicparent->ptexFeedback; ptextarg->ptexFeedback->AddRef();
						ptextarg->psurf = ptextarg->pmimicparent->psurf;
						ptextarg->psurfFeedback = ptextarg->pmimicparent->psurfFeedback; ptextarg->psurfFeedback->AddRef();
					}
					else
						curvb.prndr->CreateFeedback();

					pd3dDevice->SetRenderTarget(1, (s_bWriteDepth && curvb.pdepth != NULL) ? curvb.pdepth->psurf : NULL);
				}
			}
		}
		else ptextarg = NULL;
	}

#ifdef _DEBUG
	if( g_bSaveFlushedFrame & 0x80000000 ) {
		char str[255];
		sprintf(str, "rndr.tga", g_SaveFrameNum);
		D3DXSaveSurfaceToFile(str, D3DXIFF_TGA, curvb.prndr->psurf, NULL, NULL);
	}
#endif

	if( conf.options & GSOPTION_WIREFRAME ) {
		// always render first few geometry as solid
		if( s_nWireframeCount > 0 ) {
			SETRS(D3DRS_FILLMODE, D3DFILL_SOLID);
		}
	}

	if( !curvb.bVarsSetTarg )
		SetContextTarget(context);
	else {
		assert( curvb.pdepth != NULL );
		
		if( curvb.pdepth->status & CRenderTarget::TS_Virtual) {
		
			if( !curvb.zbuf.zmsk ) {
				CRenderTarget* ptemp = s_DepthRTs.Promote(curvb.pdepth->fbp|(curvb.pdepth->fbw<<16));
				assert( ptemp == curvb.pdepth );
			}
			else
				curvb.pdepth->status &= ~CRenderTarget::TS_NeedUpdate;
		}

		if( (curvb.pdepth->status & CRenderTarget::TS_NeedUpdate) || (curvb.prndr->status & CRenderTarget::TS_NeedUpdate) )
			SetContextTarget(context);
	}

	SetTexVariables(context);
	if( ptextarg == NULL && pmemtarg == NULL ) {
		pmemtarg = g_MemTargs.GetMemoryTarget(curvb.tex0, 1);

		if( vb[context].bVarsTexSync ) {
			if( vb[context].pmemtarg != pmemtarg ) {
				SetTexVariablesInt(context, GetTexFilter(curvb.tex1), curvb.tex0, pmemtarg, s_bForceTexFlush);
				vb[context].bVarsTexSync = TRUE;
			}
		}
		else {
			SetTexVariablesInt(context, GetTexFilter(curvb.tex1), curvb.tex0, pmemtarg, s_bForceTexFlush);	
			vb[context].bVarsTexSync = TRUE;

			INC_TEXVARS();
		}
	}

	icurctx = context;

	assert( !(curvb.prndr->status&CRenderTarget::TS_NeedUpdate) );
	curvb.prndr->status = 0;
	
	if( curvb.pdepth != NULL ) {
		assert( !(curvb.pdepth->status&CRenderTarget::TS_NeedUpdate) );
		if( !curvb.zbuf.zmsk ) {
			assert( !(curvb.pdepth->status & CRenderTarget::TS_Virtual) );
			curvb.pdepth->status = 0;
		}
	}

	s_dwColorWrite = (curvb.prndr->psm&0xf) == 1 ? (D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED) : 0xf;

	if( ((curvb.frame.fbm)&0xff) == 0xff) s_dwColorWrite &= ~D3DCOLORWRITEENABLE_RED;
	if( ((curvb.frame.fbm>>8)&0xff) == 0xff) s_dwColorWrite &= ~D3DCOLORWRITEENABLE_GREEN;
	if( ((curvb.frame.fbm>>16)&0xff) == 0xff) s_dwColorWrite &= ~D3DCOLORWRITEENABLE_BLUE;
	if( ((curvb.frame.fbm>>24)&0xff) == 0xff) s_dwColorWrite &= ~D3DCOLORWRITEENABLE_ALPHA;

	SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);

	pd3dDevice->SetScissorRect(&curvb.prndr->scissorrect); // need to always set it since something in this code resets it

	// set the shaders
	pd3dDevice->SetVertexShader(pvs[2*((curvb.curprim._val>>1)&3)+8*s_bWriteDepth+context]);
	pd3dDevice->SetStreamSource(0, curvb.pvb, curvb.dwCurOff*sizeof(VertexGPU), sizeof(VertexGPU));

	DWORD dwUsingSpecialTesting = 0;
	DWORD dwFilterOpts = 0;

	IDirect3DPixelShader9* pps;

	// need exact if equal or notequal
	int exactcolor = 0;
	if( g_nPixelShaderVer != SHADER_20 )
		// ffx2 breaks when ==7
		exactcolor = (curtest.ate && curtest.aref <= 128) && (curtest.atst==4);//||curtest.atst==7);

	int shadertype = 0;

	// set the correct pixel shaders
	if( curvb.curprim.tme ) {

		if( curvb.ptexClamp[0] != NULL ) pd3dDevice->SetTexture(SAMP_BITWISEANDX, curvb.ptexClamp[0]);
		if( curvb.ptexClamp[1] != NULL ) pd3dDevice->SetTexture(SAMP_BITWISEANDY, curvb.ptexClamp[1]);

		if( ptexRenderTargetCached != NULL ) {
			DXVEC4 vpageoffset;
			vpageoffset.w = 0;

			int psm = curvb.tex0.psm;
			assert( !PSMT_ISCLUT(curvb.tex0.psm));

			pps = LoadShadeEffect(1, 0, curvb.curprim.fge, curvb.tex0.tcc && gs.texa.aem && (psm == PSMCT24 || psm == PSMCT16 || psm == PSMCT16S),
				exactcolor, curvb.clamp, context);
			pd3dDevice->SetTexture(SAMP_MEMORY0+context, ptexRenderTargetCached);
			s_ptexCurSet[context] = ptexRenderTargetCached;

			if( curvb.tex1.mmag ) {
				pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
				dwFilterOpts |= 1;
			}
			
			if( curvb.tex1.mmin ) {
				pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
				dwFilterOpts |= 2;
			}

			DXVEC4 vTexDims;
			vTexDims.x = curvb.tex0.tw / (float)cachedtbw;
			vTexDims.y = curvb.tex0.th / (float)cachedtbh;

//			u32 tbp0 = curvb.tex0.tbp0 >> 5; // align to a page
//			int blockheight = 32;
//			int ycoord = ((curvb.tex0.tbp0-cachedtbp0)/(32*(cachedtbw>>6))) * blockheight;
//			int xcoord = (((curvb.tex0.tbp0-cachedtbp0)%(32*(cachedtbw>>6)))) * 2;
////				xcoord += ptextarg->targoffx;
////				ycoord += ptextarg->targoffy;
//			vTexDims.z = (float)xcoord / (float)cachedtbw;
//			vTexDims.w = (float)ycoord / (float)cachedtbh;
			vTexDims.z = vTexDims.w = 0;
			SETCONSTF(GPU_TEXDIMS0+context, vTexDims);

			SETCONSTF(GPU_PAGEOFFSET0+context, vpageoffset);

			if( g_bSaveTex )
				D3DXSaveTextureToFile("tex.tga", D3DXIFF_TGA, ptexRenderTargetCached, NULL);
		}
		else if( ptextarg != NULL ) {

			if( ptextarg->IsDepth() )
				SetWriteDepth();

			DXVEC4 vpageoffset;
			vpageoffset.w = 0;

			shadertype = 1;
			if( (curvb.tex0.psm == PSMT8 || curvb.tex0.psm == PSMT8H) && !(g_GameSettings&GAME_NOTARGETCLUT) ) {
				// load the clut to memory
				LPD3DTEX ptexclut = NULL;
				pd3dDevice->CreateTexture(256, 1, 1, 0, (curvb.tex0.cpsm&2) ? D3DFMT_A1R5G5B5 : D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &ptexclut, NULL);
				if( ptexclut != NULL ) {

					D3DLOCKED_RECT lock;
					ptexclut->LockRect(0, &lock, NULL, D3DLOCK_NOSYSLOCK);

					// fill the buffer by decoding the clut
					int nClutOffset = 0, clutsize;
					int entries = (curvb.tex0.psm&3)==3 ? 256 : 16;
					if( curvb.tex0.cpsm <= 1 ) { // 32 bit
						nClutOffset = 64 * curvb.tex0.csa;
						clutsize = min(entries, 256-curvb.tex0.csa*16)*4;
					}
					else {
						nClutOffset = 64 * (curvb.tex0.csa&15) + (curvb.tex0.csa>=16?2:0);
						clutsize = min(entries, 512-curvb.tex0.csa*16)*2;
					}

					if( curvb.tex0.cpsm <= 1 ) { // 32 bit
						memcpy_amd(lock.pBits, ZeroGS::g_pbyGSClut+nClutOffset, clutsize);
					}
					else {
						u16* pClutBuffer = (u16*)(ZeroGS::g_pbyGSClut + nClutOffset);
						u16* pclut = (u16*)lock.pBits;
						int left = ((u32)nClutOffset & 2) ? 0 : ((nClutOffset&0x3ff)/2)+clutsize-512;
						if( left > 0 ) clutsize -= left;

						while(clutsize > 0) {
							pclut[0] = pClutBuffer[0];
							pclut++;
							pClutBuffer+=2;
							clutsize -= 2;
						}

						if( left > 0) {
							pClutBuffer = (u16*)(ZeroGS::g_pbyGSClut + 2);
							while(left > 0) {
								pclut[0] = pClutBuffer[0];
								left -= 2;
								pClutBuffer += 2;
								pclut++;
							}
						}
					}

					ptexclut->UnlockRect(0);

					s_vecTempTextures.push_back(ptexclut);
					pd3dDevice->SetTexture(SAMP_FINAL, ptexclut);

					if( g_bSaveTex )
						D3DXSaveTextureToFile("clut.tga", D3DXIFF_TGA, ptexclut, NULL);
				}

				if( g_nPixelShaderVer != SHADER_20 && (ptextarg->psm & 2) ) {
					// 16 bit texture
					shadertype = 4;

					DXVEC4 v;
					v.x = 16.0f / (float)ptextarg->fbw;
					v.y = 64.0f / (float)ptextarg->fbh;
					v.z = 0.5f * v.x;
					v.w = 0.5f * v.y;
					SETCONSTF(GPU_TEXOFFSET0, v);

					v.x = 1;
					v.y = -0.5f;
					v.z = 0;
					v.w = 0.0001f;
					SETCONSTF(GPU_PAGEOFFSET0, v);

					pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
					pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
					pd3dDevice->SetTexture(SAMP_BILINEARBLOCKS, ptexConv32to16);
				}
				else
					shadertype = 2;
			}
			else {
				if( PSMT_ISCLUT(curvb.tex0.psm) )
					WARN_LOG("Using render target with CLUTs %d!\n", curvb.tex0.psm);
				else {
					if( (curvb.tex0.psm&2) != (ptextarg->psm&2) && (g_nPixelShaderVer != SHADER_20 || !curvb.curprim.fge) ) {
						if( curvb.tex0.psm & 2 ) {
							// converting from 32->16
							shadertype = 3;

							DXVEC4 v;
							v.x = 16.0f / (float)curvb.tex0.tw;
							v.y = 64.0f / (float)curvb.tex0.th;
							v.z = 0.5f * v.x;
							v.w = 0.5f * v.y;
							SETCONSTF(GPU_TEXOFFSET0+context, v);

							vpageoffset.x = -0.1f / 256.0f;
							vpageoffset.y = -0.001f / 256.0f;
							vpageoffset.z = -0.1f / ptextarg->fbh;
							vpageoffset.w = ((ptextarg->psm&0x30)==0x30)?-1.0f:0.0f;

							pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
							pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
							pd3dDevice->SetTexture(SAMP_BILINEARBLOCKS, ptexConv16to32);
						}
						else {
							// converting from 16->32
							WARN_LOG("ZeroGS: converting from 16 to 32bit RTs\n");
							//shadetype = 4;
						}
					}
				}
			}

			int psm = curvb.tex0.psm;
			if( PSMT_ISCLUT(curvb.tex0.psm) ) psm = curvb.tex0.cpsm;

			pps = LoadShadeEffect(shadertype, 0, curvb.curprim.fge, curvb.tex0.tcc && gs.texa.aem && (psm == PSMCT24 || psm == PSMCT16 || psm == PSMCT16S),
				exactcolor, curvb.clamp, context);
			LPD3DTEX ptexset = ptextarg == curvb.prndr ? ptextarg->ptexFeedback : ptextarg->ptex;
			pd3dDevice->SetTexture(SAMP_MEMORY0+context, ptexset);
			s_ptexCurSet[context] = ptexset;

			if( curvb.tex1.mmag ) {
				pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
				dwFilterOpts |= 1;
			}
			
			if( curvb.tex1.mmin ) {
				pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
				dwFilterOpts |= 2;
			}

			DXVEC4 vTexDims;
			vTexDims.x = curvb.tex0.tw / (float)ptextarg->fbw;
			vTexDims.y = curvb.tex0.th / (float)ptextarg->targheight;

			// look at the offset of tbp0 from fbp
			if( curvb.tex0.tbp0 <= ptextarg->fbp ) {
				vTexDims.z = 0;//-0.5f/(float)ptextarg->fbw;
				vTexDims.w = 0;//0.2f/(float)ptextarg->fbh;
			}
			else {
				u32 tbp0 = curvb.tex0.tbp0 >> 5; // align to a page
				int blockheight = (ptextarg->psm&2) ? 64 : 32;
				int ycoord = ((curvb.tex0.tbp0-ptextarg->fbp)/(32*(ptextarg->fbw>>6))) * blockheight;
				int xcoord = (((curvb.tex0.tbp0-ptextarg->fbp)%(32*(ptextarg->fbw>>6)))) * 2;
				xcoord += ptextarg->targoffx;
				ycoord += ptextarg->targoffy;
				vTexDims.z = (float)xcoord / (float)ptextarg->fbw;
				vTexDims.w = (float)ycoord / (float)ptextarg->targheight;
			}

			if( shadertype == 4 ) {
				vTexDims.z += 8.0f / (float)ptextarg->fbw;
			}

			SETCONSTF(GPU_TEXDIMS0+context, vTexDims);

			// zoe2
			if( (ptextarg->psm&0x30) == 0x30 ) {//&& (psm&2) == (ptextarg->psm&2) ) {
				// target of zbuf has +1 added to it, don't do 16bit
				vpageoffset.w = -1;
//				DXVEC4 valpha2;
//				valpha2.x = 1; valpha2.y = 0;
//				valpha2.z = -1; valpha2.w = 0;
//				SETCONSTF(GPU_TEXALPHA20+context, &valpha2);
			}
			SETCONSTF(GPU_PAGEOFFSET0+context, vpageoffset);

			if( g_bSaveTex )
				D3DXSaveTextureToFile("tex.tga", D3DXIFF_TGA, ptextarg == curvb.prndr ? ptextarg->ptexFeedback : ptextarg->ptex, NULL);
		}
		else {
			// save the texture
#ifdef _DEBUG
//			CMemoryTarget* pmemtarg = g_MemTargs.GetMemoryTarget(curvb.tex0, 0);
//			assert( curvb.pmemtarg == pmemtarg );
//			if( PSMT_ISCLUT(curvb.tex0.psm) )
//				assert( curvb.pmemtarg->ValidateClut(curvb.tex0) );
#endif

//#ifdef ZEROGS_CACHEDCLEAR
//			if( !curvb.pmemtarg->ValidateTex(curvb.tex0, true) ) {
//				CMemoryTarget* pmemtarg = g_MemTargs.GetMemoryTarget(curvb.tex0, 1);
//				SetTexVariablesInt(context, GetTexFilter(curvb.tex1), curvb.tex0, pmemtarg, s_bForceTexFlush);
//				vb[context].bVarsTexSync = TRUE;
//			}
//#endif

			if( g_bSaveTex ) {
				if( g_bSaveTex == 1 ) SaveTex(&curvb.tex0, 1);
				else SaveTex(&curvb.tex0, 0);
			}

			int psm = curvb.tex0.psm;
			if( PSMT_ISCLUT(curvb.tex0.psm) ) psm = curvb.tex0.cpsm;

			pps = LoadShadeEffect(0, GetTexFilter(curvb.tex1), curvb.curprim.fge,
				curvb.tex0.tcc && gs.texa.aem && (psm == PSMCT24 || psm == PSMCT16 || psm == PSMCT16S),
				exactcolor, curvb.clamp, context);
		}
	}
	else pps = ppsRegular[curvb.curprim.fge+2*s_bWriteDepth];

	pd3dDevice->SetPixelShader(pps);
		
	BOOL bCanRenderStencil = g_bUpdateStencil && (curvb.prndr->psm&0xf) != 1 && !(curvb.frame.fbm&0x80000000);
	if( g_GameSettings & GAME_NOSTENCIL )
		bCanRenderStencil = 0;

	if( s_bDestAlphaTest) {
		SETRS(D3DRS_STENCILENABLE, bCanRenderStencil);		
		SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
		SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
	}
	else SETRS(D3DRS_STENCILENABLE, 0);

	SETRS(D3DRS_ZWRITEENABLE, !curvb.zbuf.zmsk);
	SETRS(D3DRS_ZENABLE, curtest.zte);
	if( curtest.zte ) {
		if( curtest.ztst > 1 ) g_nDepthUsed = 2;
		if( (curtest.ztst == 2) ^ (g_nDepthBias != 0) ) {
			g_nDepthBias = curtest.ztst == 2;
			if( g_GameSettings & GAME_RELAXEDDEPTH )
				SETRS(D3DRS_DEPTHBIAS, g_nDepthBias?FtoDW(0.00003f):FtoDW(0.0001f));
			else
				SETRS(D3DRS_DEPTHBIAS, g_nDepthBias?FtoDW(0.0003f):FtoDW(0.000015f));
		}

		SETRS(D3DRS_ZFUNC, g_dwZCmp[curtest.ztst]);

//		if( curtest.ztst == 3 ) {
//			// gequal
//			if( s_vznorm.y == 0 ) {
//				s_vznorm.y = 0.00001f;
//				SETCONSTF(GPU_ZNORM, s_vznorm);
//			}
//		}
//		else {
//			if( s_vznorm.y > 0 ) {
//				s_vznorm.y = 0;
//				SETCONSTF(GPU_ZNORM, s_vznorm);
//			}
//		}
	}

	SETRS(D3DRS_ALPHATESTENABLE, curtest.ate&&USEALPHATESTING);
	if( curtest.ate ) {

		if( curtest.atst == 7 && curtest.aref == 255 ) {
			// when it is at the very top, do a less than rather than not equal (gekibo2)
			SETRS(D3DRS_ALPHAFUNC, D3DCMP_LESS);
			SETRS(D3DRS_ALPHAREF, 255);
		}
		else {
			SETRS(D3DRS_ALPHAFUNC, g_dwAlphaCmp[curtest.atst]);
			SETRS(D3DRS_ALPHAREF, b2XAlphaTest ? min(255,2 * curtest.aref) : curtest.aref);
		}
	}

	if( s_bWriteDepth ) {
		//pd3dDevice->SetRenderTarget(0, curvb.prndr->psurf);
		//pd3dDevice->SetRenderTarget(1, !curvb.zbuf.zmsk?curvb.pdepth->psurf:NULL);
		if( bIndepWriteMasks )
			SETRS(D3DRS_COLORWRITEENABLE1, !curvb.zbuf.zmsk?0xf:0);
		else
			pd3dDevice->SetRenderTarget(1, !curvb.zbuf.zmsk?curvb.pdepth->psurf:NULL);
	}

	if( curvb.curprim.abe )
		SetAlphaVariables(curvb.alpha);
	else
		SETRS(D3DRS_ALPHABLENDENABLE, 0);

	// needs to be before RenderAlphaTest
	if( curvb.fba.fba || s_bDestAlphaTest ) {
		if( gs.pabe || (curvb.fba.fba || bCanRenderStencil) && !(curvb.frame.fbm&0x80000000) ) {
			RenderFBA(curvb);
		}
	}

	u32 oldabe = curvb.curprim.abe;
	if( gs.pabe ) {
		//WARN_LOG("PBE!\n");
		curvb.curprim.abe = 1;
		SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING);
	}

	if( curvb.curprim.abe && bNeedAlphaColor ) {

		if( //bCanRenderStencil &&
			(bNeedBlendFactorInAlpha || ((curtest.ate && curtest.atst>1) && (curtest.aref > 0x80))) ) {
			// need special stencil processing for the alpha
			RenderAlphaTest(curvb);
			dwUsingSpecialTesting = 1;
		}

		// harvest fishing
		DXVEC4 v = vAlphaBlendColor;// + DXVEC4(0,0,0,(curvb.test.atst==4 && curvb.test.aref>=128)?-0.004f:0);
		if( exactcolor ) { v.y *= 255; v.w *= 255; }
		SETCONSTF(GPU_ONECOLOR, v);
	}
	else {
		// not using blending so set to defaults
		DXVEC4 v = exactcolor ? DXVEC4(1, 510*255.0f/256.0f, 0, 0) : DXVEC4(1,2*255.0f/256.0f,0,0);
		SETCONSTF(GPU_ONECOLOR, v);
	}

	if( s_bDestAlphaTest && bCanRenderStencil ) {
		// if not 24bit and can write to high alpha bit
		RenderStencil(curvb, dwUsingSpecialTesting);
	}
	else {
		dwStencilRef = STENCIL_SPECIAL;
		dwStencilMask = STENCIL_SPECIAL;

		// setup the stencil to only accept the test pixels
		if( dwUsingSpecialTesting ) {
			SETRS(D3DRS_STENCILENABLE, TRUE);
			SETRS(D3DRS_STENCILWRITEMASK, STENCIL_PIXELWRITE);
			SETRS(D3DRS_STENCILMASK, STENCIL_SPECIAL);
			SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
			SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
			SETRS(D3DRS_STENCILREF, STENCIL_SPECIAL|STENCIL_PIXELWRITE);
		}
	}

#ifdef _DEBUG
	if( bDestAlphaColor == 1 ) {
		WARN_LOG("dest alpha blending! manipulate alpha here\n");
	}
#endif

	if( bCanRenderStencil && gs.pabe ) {
		// only render the pixels with alpha values >= 0x80
		SETRS(D3DRS_STENCILREF, dwStencilRef|STENCIL_FBA);
		SETRS(D3DRS_STENCILMASK, dwStencilMask|STENCIL_FBA);
		if( !dwStencilMask ) SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
	}

//	curvb.prndr->SetViewport();
//	pd3dDevice->SetScissorRect(&curvb.prndr->scissorrect);
//	SETRS(D3DRS_SCISSORTESTENABLE, TRUE);

	if( !curvb.test.ate || curvb.test.atst > 0 ) {
		DRAW();
	}

	if( gs.pabe ) {
		// only render the pixels with alpha values < 0x80
		SETRS(D3DRS_ALPHABLENDENABLE, 0);
		SETRS(D3DRS_STENCILREF, dwStencilRef);

		DXVEC4 v;
		v.x = 1; v.y = 2; v.z = 0; v.w = 0;
		if( exactcolor ) v.y *= 255;
		SETCONSTF(GPU_ONECOLOR, v);

		DRAW();

		// reset
		SETRS(D3DRS_STENCILMASK, dwStencilMask);
		if( !dwStencilMask ) SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
	}

	// more work on alpha failure case
	if( curtest.ate && curtest.atst != 1 && curtest.afail > 0 ) {

		// need to reverse the test and disable some targets
		SETRS(D3DRS_ALPHAFUNC, g_dwReverseAlphaCmp[curtest.atst]);

		if( curtest.afail & 1 ) {	// front buffer update only
			
			if( curtest.afail == 3 ) // disable alpha
				SETRS(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_RED);

			SETRS(D3DRS_ZWRITEENABLE, FALSE);

			if( s_bWriteDepth ) {
				if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
				else pd3dDevice->SetRenderTarget(1,NULL);
			}
		}
		else {
			// zbuffer update only
			SETRS(D3DRS_COLORWRITEENABLE, 0);	
		}

		if( gs.pabe && bCanRenderStencil ) {
			// only render the pixels with alpha values >= 0x80
			DXVEC4 v = vAlphaBlendColor;
			if( exactcolor ) { v.y *= 255; v.w *= 255; }
			SETCONSTF(GPU_ONECOLOR, v);
			SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING);
			SETRS(D3DRS_STENCILREF, dwStencilRef|STENCIL_FBA);
			SETRS(D3DRS_STENCILMASK, dwStencilMask|STENCIL_FBA);
			if( !dwStencilMask ) SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
		}

		// setup the stencil to only accept the test pixels
		if( dwUsingSpecialTesting ) {

			if( !s_bDestAlphaTest || !bCanRenderStencil ) {
				SETRS(D3DRS_STENCILENABLE, FALSE);
			}
		}

//		IDirect3DQuery9* pOcclusionQuery;
//		DWORD numberOfPixelsDrawn;
//
//		pd3dDevice->CreateQuery(D3DQUERYTYPE_OCCLUSION, &pOcclusionQuery);
//
//		// Add an end marker to the command buffer queue.
//		pOcclusionQuery->Issue(D3DISSUE_BEGIN);

		DRAW();

//		pOcclusionQuery->Issue(D3DISSUE_END);
		// Force the driver to execute the commands from the command buffer.
		// Empty the command buffer and wait until the GPU is idle.
//		while(S_FALSE == pOcclusionQuery->GetData( &numberOfPixelsDrawn, sizeof(DWORD), D3DGETDATA_FLUSH ));
//		SAFE_RELEASE(pOcclusionQuery);
	
		if( gs.pabe ) {
			// only render the pixels with alpha values < 0x80
			SETRS(D3DRS_ALPHABLENDENABLE, 0);
			SETRS(D3DRS_STENCILREF, dwStencilRef);

			DXVEC4 v;
			v.x = 1; v.y = 2; v.z = 0; v.w = 0;
			if( exactcolor ) v.y *= 255;
			SETCONSTF(GPU_ONECOLOR, v);

			DRAW();

			// reset
			SETRS(D3DRS_STENCILMASK, dwStencilMask);
			SETRS(D3DRS_ALPHABLENDENABLE, oldabe);
			if( !dwStencilMask ) SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
		}

		// restore
		
		if( (curtest.afail & 1) && !curvb.zbuf.zmsk ) {
			SETRS(D3DRS_ZWRITEENABLE, TRUE);

			if( s_bWriteDepth ) {
				assert( curvb.pdepth != NULL);
				if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
				else pd3dDevice->SetRenderTarget(1,curvb.pdepth->psurf);
			}
		}

		SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);

		// not needed anymore since rest of ops concentrate on image processing
		//SETRS(D3DRS_ALPHAFUNC, g_dwAlphaCmp[curtest.atst]);
	}

	if( dwUsingSpecialTesting ) {
		// render the real alpha
		SETRS(D3DRS_ALPHATESTENABLE, FALSE);
		SETRS(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA);

		if( s_bWriteDepth ) {
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
			else pd3dDevice->SetRenderTarget(1,NULL);
		}

		SETRS(D3DRS_ZWRITEENABLE, FALSE);

		SETRS(D3DRS_STENCILMASK, STENCIL_SPECIAL|STENCIL_PIXELWRITE);
		SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
		SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
		SETRS(D3DRS_STENCILREF, STENCIL_SPECIAL|STENCIL_PIXELWRITE);

		DXVEC4 v = DXVEC4(0,exactcolor ? 510.0f : 2.0f,0,0);
		SETCONSTF(GPU_ONECOLOR, v);
		DRAW();

		// don't need to restore
	}

	if( s_bDestAlphaTest ) {
		if( (s_dwColorWrite&D3DCOLORWRITEENABLE_ALPHA) ) {
			if( curvb.fba.fba )
				ProcessFBA(curvb);
			else if( bCanRenderStencil )
				// finally make sure all entries are 1 when the dest alpha >= 0x80 (if fba is 1, this is already the case)
				ProcessStencil(curvb);
		}
	}
	else if( (s_dwColorWrite&D3DCOLORWRITEENABLE_ALPHA) && curvb.fba.fba )
		ProcessFBA(curvb);

	if( bDestAlphaColor == 1 ) {
		// need to reset the dest colors to their original counter parts
		//WARN_LOG("Need to reset dest alpha color\n");
	}

#ifdef _DEBUG
	if( g_bSaveFlushedFrame & 0xf ) {
		char str[255];
		sprintf(str, "frames\\frame%.4d.jpg", g_SaveFrameNum++);
		if( (g_bSaveFlushedFrame & 2)  )
			D3DXSaveSurfaceToFile(str, D3DXIFF_JPG, curvb.prndr->psurf, NULL, NULL);
	}
#endif

	// clamp the final colors, when enabled ffx2 credits mess up
	if( curvb.curprim.abe && bAlphaClamping && g_RenderFormat != D3DFMT_A8R8G8B8 && !(g_GameSettings&GAME_NOCOLORCLAMP) ) { // if !colclamp, skip

		ResetAlphaVariables();

		// if processing the clamping case, make sure can write to the front buffer
		SETRS(D3DRS_STENCILENABLE, 0);
		SETRS(D3DRS_ALPHABLENDENABLE, TRUE);
		SETRS(D3DRS_ALPHATESTENABLE, FALSE);
		SETRS(D3DRS_ZENABLE, FALSE);
		SETRS(D3DRS_ZWRITEENABLE, FALSE);
		SETRS(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_BLUE|D3DCOLORWRITEENABLE_GREEN);
		
		if( s_bWriteDepth ) {
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
			else pd3dDevice->SetRenderTarget(1,NULL);
		}

		pd3dDevice->SetPixelShader(ppsOne);

		// (dest&0x7f)+0x80, blend factor for alpha is always 0x80
		SETRS(D3DRS_DESTBLEND, D3DBLEND_ONE);
		SETRS(D3DRS_SRCBLEND, D3DBLEND_ONE);

		float f;

		if( bAlphaClamping & 1 ) { // min
			f = 0;
			SETCONSTF(GPU_ONECOLOR, &f);
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_MAX);
			DRAW();
		}
		
		// bios shows white screen
		if( bAlphaClamping & 2 ) { // max
			f = 1;
			SETCONSTF(GPU_ONECOLOR, &f);
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_MIN);
			DRAW();
		}

		if( !curvb.zbuf.zmsk ) {
			SETRS(D3DRS_ZWRITEENABLE, TRUE);

			if( s_bWriteDepth ) {
				assert( curvb.pdepth != NULL );
				if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
				else pd3dDevice->SetRenderTarget(1,curvb.pdepth->psurf);
			}
		}

		if( curvb.test.ate && USEALPHATESTING )
			SETRS(D3DRS_ALPHATESTENABLE, TRUE);

		SETRS(D3DRS_ZENABLE, curtest.zte);
	}

	if( dwFilterOpts ) {
		// undo filter changes
		if( dwFilterOpts & 1 ) pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
		if( dwFilterOpts & 2 ) pd3dDevice->SetSamplerState(SAMP_MEMORY0+context, D3DSAMP_MINFILTER, D3DTEXF_POINT);
	}

	// reset used textures
	if( shadertype > 2 ) {
		pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
		pd3dDevice->SetSamplerState(SAMP_BILINEARBLOCKS, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
		pd3dDevice->SetTexture(SAMP_BILINEARBLOCKS, ptexBilinearBlocks);
	}

	SETRS(D3DRS_CLIPPLANEENABLE, 0);
//#ifndef RELEASE_TO_PUBLIC
	ppf += curvb.dwCount+0x100000;
//#endif
	curvb.dwCurOff += POINT_BUFFERFLUSH;
	SAFE_RELEASE(ptexRenderTargetCached);
	
	g_MaxRenderedHeight = 0;
	curvb.dwCount = 0;
	//curvb.Lock();
	curvb.curprim.abe = oldabe;
	//if( oldabe ) SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING);

	if( conf.options & GSOPTION_WIREFRAME ) {
		// always render first few geometry as solid
		if( s_nWireframeCount > 0 ) {
			SETRS(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
			--s_nWireframeCount;
		}
	}
}

void ZeroGS::ProcessMessages()
{
	if( listMsgs.size() > 0 ) {
		pSprite->Begin(D3DXSPRITE_ALPHABLEND|D3DXSPRITE_SORT_TEXTURE);

		RECT rctext;
		rctext.left = 25; rctext.top = 15;
		list<MESSAGE>::iterator it = listMsgs.begin();
		
		while( it != listMsgs.end() ) {
			rctext.left += 1;
			rctext.top += 1;
			pFont->DrawText(pSprite, it->str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xff000000);
			rctext.left -= 1;
			rctext.top -= 1;
			pFont->DrawText(pSprite, it->str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xffffff30);
			rctext.top += 15;

			if( (int)(it->dwTimeStamp - timeGetTime()) < 0 )
				it = listMsgs.erase(it);
			else ++it;
		}

		pSprite->End();
	}
}

void ZeroGS::RenderCustom(float fAlpha)
{
	if( !s_bBeginScene )
		pd3dDevice->BeginScene();

	pd3dDevice->SetDepthStencilSurface(psurfOrgDepth);

	pd3dDevice->SetRenderTarget(0, psurfOrgTarg);

	if( s_bWriteDepth )
		pd3dDevice->SetRenderTarget(1, NULL);

	SETRS(D3DRS_STENCILENABLE, 0);
	SETRS(D3DRS_ZENABLE, FALSE);
	SETRS(D3DRS_ZWRITEENABLE, FALSE);
	SETRS(D3DRS_COLORWRITEENABLE, 0xf);
	SETRS(D3DRS_ALPHABLENDENABLE, 0);
	SETRS(D3DRS_ALPHATESTENABLE, 0);
	SETRS(D3DRS_SCISSORTESTENABLE, 0);

	// play custom animation
	pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, 0, 1, 0);

	// tex coords
	DXVEC4 v = DXVEC4(1, 1, 0, 0);
	SETCONSTF(GPU_BITBLTTEX, v);
	SETCONSTF(GPU_BITBLTPOS, v);

	v.x = v.y = v.z = v.w = fAlpha;
	SETCONSTF(GPU_ONECOLOR, v);

	if( conf.options & GSOPTION_WIREFRAME ) SETRS(D3DRS_FILLMODE, D3DFILL_SOLID);

	pd3dDevice->SetVertexShader(pvsBitBlt);
	pd3dDevice->SetStreamSource(0, pvbRect, 0, sizeof(VertexGPU));
	pd3dDevice->SetPixelShader(ppsBaseTexture);

	// inside vb[0]'s target area, so render that region only
	pd3dDevice->SetTexture(SAMP_FINAL, ptexLogo);
	//pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	//pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

	pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

	// restore
	//pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
	//pd3dDevice->SetSamplerState(SAMP_FINAL, D3DSAMP_MINFILTER, D3DTEXF_POINT);
	if( conf.options & GSOPTION_WIREFRAME ) SETRS(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

	ProcessMessages();

	pd3dDevice->EndScene();
	s_bBeginScene = FALSE;

	pd3dDevice->Present(NULL, NULL, NULL, NULL);

	SETRS(D3DRS_SCISSORTESTENABLE, TRUE);
	SETRS(D3DRS_STENCILENABLE, 1);

	if( icurctx >= 0 ) vb[icurctx].bSyncVars = 0;
}

// adjusts trans to preserve aspect ratio
void ZeroGS::AdjustTransToAspect(DXVEC4& v, int dispwidth, int dispheight)
{
	float temp, f;
	if( dispwidth * height > dispheight * width ) {
		// limited by width
		
		// change in ratio
		f = ((float)width / (float)dispwidth) / ((float)height / (float)dispheight);
		v.y *= f;
		v.w *= f;

		// scanlines mess up when not aligned right
		v.y += (1-modf(v.y*height/2+0.05f, &temp))*2.0f/(float)height;
		v.w += (1-modf(v.w*height/2+0.05f, &temp))*2.0f/(float)height;
	}
	else {
		// limited by height
		f = ((float)height / (float)dispheight) / ((float)width / (float)dispwidth);
		f -= modf(f*width, &temp)/(float)width;
		v.x *= f;
		v.z *= f;
	}
}

void ZeroGS::Restore()
{
	if( !g_bIsLost )
		return;

	if( SUCCEEDED(pd3dDevice->Reset(&d3dpp)) ) {
		g_bIsLost = 0;
		// handle lost states
		ZeroGS::ChangeDeviceSize(width, height);
	}
}

void ZeroGS::RenderCRTC(int interlace)
{
	if( pd3dDevice == NULL ) {
		return;
	}

	if( g_bIsLost ) return;

#ifdef RELEASE_TO_PUBLIC
	if( g_nRealFrame < 80 ) {
		RenderCustom( min(1.0f, 2.0f - (float)g_nRealFrame / 40.0f) );

		if( g_nRealFrame == 79 )
			SAFE_RELEASE(ptexLogo);
		return;
	}
#endif

	Flush(0);
	Flush(1);

	// frame skipping
	if( g_nFrameRender > 0 ) {

		if( g_nFrameRender < 8 ) {
			g_nFrameRender++;
			if( g_nFrameRender <= 3 ) {
				g_nFramesSkipped++;
				return;
			}
		}
	}
	else {
		if( g_nFrameRender < -1 ) {
			g_nFramesSkipped++;
			return;
		}
		g_nFrameRender--;
	}

	if( g_bSaveFrame ) {
		if( vb[0].prndr != NULL ) D3DXSaveSurfaceToFile("frame1.tga", D3DXIFF_TGA, vb[0].prndr->psurf, NULL, NULL);
		
		if( vb[1].prndr != NULL && vb[0].prndr != vb[1].prndr ) D3DXSaveSurfaceToFile("frame2.tga", D3DXIFF_TGA, vb[1].prndr->psurf, NULL, NULL);
		else DeleteFile("frame2.tga");
	}

	if( s_RangeMngr.ranges.size() > 0 )
		FlushTransferRanges(NULL);

	if( icurctx >= 0 && vb[icurctx].bVarsSetTarg ) { // check if anything rendered
		pd3dDevice->SetRenderTarget(0, psurfOrgTarg);
		pd3dDevice->SetRenderTarget(1, NULL);

		pd3dDevice->SetDepthStencilSurface(psurfOrgDepth);
	}

	D3DVIEWPORT9 view;
	view.Width = width;
	view.Height = height;
	view.X = 0;
	view.Y = 0;
	view.MinZ = 0;
	view.MaxZ = 1.0f;
	pd3dDevice->SetViewport(&view);

	//g_GameSettings |= GAME_VSSHACK|GAME_FULL16BITRES|GAME_NODEPTHRESOLVE;
	//s_bWriteDepth = TRUE;
	g_SaveFrameNum = 0;
	g_bSaveFlushedFrame = 1;
//	static int counter = 0;
//	counter++;
	// reset fba after every frame
	//if( !(g_GameSettings&GAME_NOFBARESET) ) {
		vb[0].fba.fba = 0;
		vb[1].fba.fba = 0;
	//}
	u32 bInterlace = SMODE2->INT && SMODE2->FFMD && (conf.interlace<2);

	// if interlace, only clear every other vsync
	if(!bInterlace ) {
		u32 color = D3DCOLOR_ARGB(0, BGCOLOR->R, BGCOLOR->G, BGCOLOR->B);
		pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_STENCIL, color, 1, 0);
	}

	if( !s_bBeginScene ) {
		pd3dDevice->BeginScene();
		s_bBeginScene = TRUE;
	}

	pd3dDevice->SetVertexShader(pvsBitBlt);
	pd3dDevice->SetStreamSource(0, pvbRect, 0, sizeof(VertexGPU));

	if( conf.options & GSOPTION_WIREFRAME ) SETRS(D3DRS_FILLMODE, D3DFILL_SOLID);
	SETRS(D3DRS_ZENABLE, 0);
	SETRS(D3DRS_ZWRITEENABLE, 0);
	SETRS(D3DRS_COLORWRITEENABLE, 0xf);
	SETRS(D3DRS_ALPHABLENDENABLE, 0);
	SETRS(D3DRS_ALPHATESTENABLE, 0);
	SETRS(D3DRS_SCISSORTESTENABLE, 0);
	SETRS(D3DRS_STENCILENABLE, 0);

	BOOL bUsingStencil = 0;

	if( bInterlace ) g_PrevBitwiseTexX = -1; // reset since will be using

	tex0Info dispinfo[2];

	for(int i = 0; i < 2; ++i) {

		if( !(*(u32*)(PMODE) & (1<<i)) ) {
			dispinfo[i].tw = 0;
			dispinfo[i].th = 0;
			continue;
		}

		GSRegDISPFB* pfb = i ? DISPFB2 : DISPFB1;
		GSRegDISPLAY* pd = i ? DISPLAY2 : DISPLAY1;
		int magh = pd->MAGH+1;
		int magv = pd->MAGV+1;
		
		dispinfo[i].tbp0 =	pfb->FBP << 5;
		dispinfo[i].tbw = pfb->FBW << 6;
		dispinfo[i].tw = (pd->DW + 1) / magh;
		dispinfo[i].th = (pd->DH + 1) / magv;
		dispinfo[i].psm = pfb->PSM;

		// hack!!
		// 2 * dispinfo[i].tw / dispinfo[i].th <= 1, metal slug 4
		if( bInterlace && 2 * dispinfo[i].tw / dispinfo[i].th <= 1 && !(g_GameSettings&GAME_INTERLACE2X) ) {
			dispinfo[i].th >>= 1;
		}
	}

	//int dispwidth = max(dispinfo[0].tw, dispinfo[1].tw), dispheight = max(dispinfo[0].th, dispinfo[1].th);

	// hack!, CMOD != 3, gradius
//	if( SMODE2->INT && SMODE2->FFMD && SMODE1->CMOD == 3 && dispwidth <= 320)
//		dispwidth *= 2;

	// hack! makai
	//if( !bInterlace && dispheight * 2 < dispwidth ) dispheight *= 2;

	// start from the last circuit
	for(int i = !PMODE->SLBG; i >= 0; --i) {

		tex0Info& texframe = dispinfo[i];
		if( texframe.th <= 1 )
			continue;

		GSRegDISPFB* pfb = i ? DISPFB2 : DISPFB1;
		GSRegDISPLAY* pd = i ? DISPLAY2 : DISPLAY1;

		DXVEC4 v, valpha;

		if( bInterlace ) {

			texframe.th >>= 1;

			// interlace mode
			pd3dDevice->SetTexture(SAMP_INTERLACE, CreateInterlaceTex(2*texframe.th));
			
			if( interlace == (conf.interlace&1) ) {
				// pass if odd
				valpha.z = 1.0f;
				valpha.w = -0.4999f;
			}
			else {
				// pass if even
				valpha.z = -1.0f;
				valpha.w = 0.5001f;
			}
		}
		else {

			if( SMODE2->INT && SMODE2->FFMD ) {
				texframe.th >>= 1;
			}

			// always pass interlace test
			valpha.z = 0;
			valpha.w = 1;
		}

		int bpp = 4;
		if( texframe.psm == 0x12 ) bpp = 3;
		else if( texframe.psm & 2 ) bpp = 2;

		// get the start and end addresses of the buffer
		int start, end;
		GetRectMemAddress(start, end, texframe.psm, 0, 0, texframe.tw, texframe.th, texframe.tbp0, texframe.tbw);

		if( i == 0 ) {
			// setup right blending
			SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING);
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
			SETRS(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);

			if( PMODE->MMOD ) {
				SETRS(D3DRS_BLENDFACTOR, D3DCOLOR_ARGB(0x80, PMODE->ALP, PMODE->ALP, PMODE->ALP));
				SETRS(D3DRS_SRCBLEND, D3DBLEND_BLENDFACTOR);
				SETRS(D3DRS_DESTBLEND, D3DBLEND_INVBLENDFACTOR);
			}
			else {
				SETRS(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
				SETRS(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
			}

			SETRS(D3DRS_SRCBLENDALPHA, PMODE->AMOD ? D3DBLEND_ZERO : D3DBLEND_ONE);
			SETRS(D3DRS_DESTBLENDALPHA, PMODE->AMOD? D3DBLEND_ONE : D3DBLEND_ZERO);
		}

		if( bUsingStencil ) {
			SETRS(D3DRS_STENCILWRITEMASK, 1<<i);
			SETRS(D3DRS_STENCILMASK, 1<<i);
		}

		if( texframe.psm == 0x12 ) {
			WARN_LOG("CRTC24!!!\n");
			// assume that data is already in ptexMem (do Resolve?)
			pd3dDevice->SetPixelShader(ppsCRTC24[bInterlace]);
			valpha.x = 0;
			valpha.y = 1;
			SETCONSTF(GPU_ONECOLOR, valpha);
			pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
			continue;
		}
		
		// first render the current render targets, then from ptexMem
		if( texframe.psm == 1 ) {
			valpha.x = 0;
			valpha.y = 1;
		}
		else {
			valpha.x = 1;
			valpha.y = 0;
		}

		SETCONSTF(GPU_ONECOLOR, valpha);

		BOOL bSkip = 0;
		BOOL bResolveTargs = 1;

		//s_mapFrameHeights[s_nCurFrameMap][texframe.tbp0] = texframe.th;
		list<CRenderTarget*> listTargs;

		s_RTs.GetTargs(start, end, listTargs);

		for(list<CRenderTarget*>::iterator it = listTargs.begin(); it != listTargs.end(); ) {
	
			CRenderTarget* ptarg = *it;

			if( ptarg->fbw == texframe.tbw && !(ptarg->status&CRenderTarget::TS_NeedUpdate) && ((256/bpp)*(texframe.tbp0-ptarg->fbp))%texframe.tbw == 0 ) {

				if( ptarg->fbp != texframe.tbp0 ) {
					// look for a better target (metal slug 5)
					list<CRenderTarget*>::iterator itbetter;
					for(itbetter = listTargs.begin(); itbetter != listTargs.end(); ++itbetter ) {
						if( (*itbetter)->fbp == texframe.tbp0 )
							break;
					}

					if( itbetter != listTargs.end() ) {
						it = listTargs.erase(it);
						continue;
					}
				}
				
				static int sindex = 0;
				char strtemp[25];
				sprintf(strtemp, "frames/frame%d.jpg", sindex++);
//				D3DXSaveSurfaceToFile(strtemp, D3DXIFF_JPG, ptarg->psurf, NULL, NULL);
//				if( g_bSaveFinalFrame )
//					D3DXSaveSurfaceToFile("frame1.tga", D3DXIFF_TGA, ptarg->psurf, NULL, NULL);

				int dby = pfb->DBY;
				int movy = 0;

				// determine the rectangle to render
				if( ptarg->fbp < texframe.tbp0 ) {
					dby += (256/bpp)*(texframe.tbp0-ptarg->fbp)/texframe.tbw;
				}
				else if( ptarg->fbp > texframe.tbp0 ) {
					dby -= (256/bpp)*(ptarg->fbp-texframe.tbp0)/texframe.tbw;

					if( dby < 0 ) {
						movy = -dby;
						dby = 0;
					}
				}

				int dh = min(ptarg->fbh - dby, texframe.th-movy);

				if( dh >= 64 ) {

					if( ptarg->fbh - dby < texframe.th-movy && !bUsingStencil ) {

						if( !bUsingStencil ) {
							pd3dDevice->Clear(0, NULL, D3DCLEAR_STENCIL, 0, 1, 0);
						}
						bUsingStencil = 1;
						SETRS(D3DRS_STENCILENABLE, TRUE);
						SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
						SETRS(D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL);
						SETRS(D3DRS_STENCILREF, 3);
						SETRS(D3DRS_STENCILWRITEMASK, 1<<i);
						SETRS(D3DRS_STENCILMASK, 1<<i);
					}

					float fiw = 1.0f / texframe.tbw;
					float fih = 1.0f / ptarg->fbh;

					// tex coords
					v = DXVEC4(fiw*(float)texframe.tw, fih*(float)(dh), fiw*(float)(pfb->DBX), fih*((float)dby-0.5f));
					SETCONSTF(GPU_BITBLTTEX, v);

					// dest rect
					v.x = 1;
					v.y = dh/(float)texframe.th;
					v.z = 0;
					v.w = 1-v.y;
					
					if( movy > 0 )
						v.w -= movy/(float)texframe.th;

					if (bInterlace && interlace == (conf.interlace&1) ) {
						// move down by 1 pixel
						v.w += 1.0f / (float)dh;
					}

					AdjustTransToAspect(v, (conf.options&GSOPTION_WIDESCREEN)?960:640, (conf.options&GSOPTION_WIDESCREEN)?540:480);
					SETCONSTF(GPU_BITBLTPOS, v);

					// use GPU_INVTEXDIMS to store inverse texture dims
					v.x = fiw;
					v.y = fih;
					v.z = 0;
					SETCONSTF(GPU_INVTEXDIMS, v);

					// inside vb[0]'s target area, so render that region only
					pd3dDevice->SetTexture(SAMP_FINAL, ptarg->ptex);
					pd3dDevice->SetPixelShader(ppsCRTCTarg[bInterlace]);

					pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

					if( abs(dh - (int)texframe.th) <= 1 ) {
						bSkip = 1;
						break;
					}
					if( abs(dh - (int)ptarg->fbh) <= 1 ) {
						it = listTargs.erase(it);
						continue;
					}
				}
			}
		
			++it;
		}

		if( !bSkip ) {
			for(list<CRenderTarget*>::iterator it = listTargs.begin(); it != listTargs.end(); ++it)
				(*it)->Resolve();

			// context has to be 0
			SetTexVariablesInt(0, 2, texframe, g_MemTargs.GetMemoryTarget(texframe, 1), 1);

			if( g_bSaveFinalFrame )
				SaveTex(&texframe, g_bSaveFinalFrame-1>0);

			// finally render from the memory (note that the stencil buffer will keep previous regions)
			v = DXVEC4(1,1,0,0);

			if (bInterlace && interlace == (conf.interlace)) {
				// move down by 1 pixel
				v.w += 1.0f / (float)texframe.th;
			}

			AdjustTransToAspect(v, (conf.options&GSOPTION_WIDESCREEN)?960:640, (conf.options&GSOPTION_WIDESCREEN)?540:480);
			SETCONSTF(GPU_BITBLTPOS, v);

			v = DXVEC4(texframe.tw,texframe.th,-0.5f,-0.5f);
			SETCONSTF(GPU_BITBLTTEX, v);

			// use GPU_INVTEXDIMS to store inverse texture dims
			v.x = 1.0f / (float)texframe.tw;
			v.y = 1.0f / (float)texframe.th;
			v.z = 0;//-0.5f * v.x;
			v.w = -0.5f * v.y;
			SETCONSTF(GPU_INVTEXDIMS, v);
			
			pd3dDevice->SetPixelShader(ppsCRTC[bInterlace]);
			pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
		}
	}

	if(1) {// || !bInterlace) {
		s_bBeginScene = FALSE;

		ProcessMessages();

		if( g_bMakeSnapshot ) {
			RECT rctext;
			char str[64];
			rctext.left = 200; rctext.top = 15;
			sprintf(str, "ZeroGS %d.%d.%d - %.1f fps %s", revision, build, minor, fFPS, s_frameskipping?" - frameskipping":"");

			pSprite->Begin(D3DXSPRITE_ALPHABLEND|D3DXSPRITE_SORT_TEXTURE);
			rctext.left += 1;
			rctext.top += 1;
			pFont->DrawText(pSprite, str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xff000000);
			rctext.left -= 1;
			rctext.top -= 1;
			pFont->DrawText(pSprite, str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xffc0ffff);
			pSprite->End();
		}

		if( g_bDisplayFPS ) {
			RECT rctext;
			char str[64];
			rctext.left = 10; rctext.top = 10;
			sprintf(str, "%.1f fps", fFPS);

			pSprite->Begin(D3DXSPRITE_ALPHABLEND|D3DXSPRITE_SORT_TEXTURE);
			rctext.left += 1;
			rctext.top += 1;
			pFont->DrawText(pSprite, str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xff000000);
			rctext.left -= 1;
			rctext.top -= 1;
			pFont->DrawText(pSprite, str, -1, &rctext, DT_LEFT|DT_NOCLIP, 0xffc0ffff);
			pSprite->End();
		}

		pd3dDevice->EndScene();
		if( pd3dDevice->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST ) {
			// device is lost, need to recreate
			DEBUG_LOG("device lost\n");
			g_bIsLost = TRUE;
			Reset();
			return;
		}

		if( conf.options & GSOPTION_WIREFRAME ) {
			// clear all targets
			s_nWireframeCount = 1;
		}

		if( g_bMakeSnapshot ) {
			
			if( SUCCEEDED(D3DXSaveSurfaceToFile(strSnapshot != ""?strSnapshot.c_str():"temp.jpg", (conf.options&GSOPTION_BMPSNAP)?D3DXIFF_BMP:D3DXIFF_JPG, psurfOrgTarg, NULL, NULL)) ) {
				char str[255];
				sprintf(str, "saved %s\n", strSnapshot.c_str());
				AddMessage(str, 500);
			}
			g_bMakeSnapshot = 0;
		}

		if( s_avicapturing ) {
			CaptureFrame();
		}

		if( s_nNewWidth >= 0 && s_nNewHeight >= 0 && !g_bIsLost ) {
			Reset();
			ChangeDeviceSize(s_nNewWidth, s_nNewHeight);
			s_nNewWidth = s_nNewHeight = -1;
		}

		// switch the fbp lists
//		s_nCurFBPSet ^= 1;
//		s_setFBP[s_nCurFBPSet].clear();
		//s_nCurFrameMap ^= 1;
		//s_mapFrameHeights[s_nCurFrameMap].clear();
	}

	pd3dDevice->SetTexture(SAMP_FINAL, NULL); // d3d debug complains if not
	g_MemTargs.DestroyCleared();

	for(list<LPD3DTEX>::iterator it = s_vecTempTextures.begin(); it != s_vecTempTextures.end(); ++it)
		(*it)->Release();
	s_vecTempTextures.clear();

	if( EXTWRITE->WRITE&1 ) {
		WARN_LOG("EXTWRITE\n");
		ExtWrite();
		EXTWRITE->WRITE = 0;
	}

	if( conf.options & GSOPTION_WIREFRAME ) SETRS(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
	SETRS(D3DRS_SCISSORTESTENABLE, TRUE);

	if( icurctx >= 0 ) {
		vb[icurctx].bVarsSetTarg = FALSE;
		vb[icurctx].bVarsTexSync = FALSE;
		vb[0].bVarsTexSync = FALSE;
	}

	// statistics
	if( s_nWriteDepthCount > 0 ) {
		assert( conf.mrtdepth );
		if( --s_nWriteDepthCount <= 0 ) {
			s_bWriteDepth = FALSE;
		}
	}

	if( s_nWriteDestAlphaTest > 0 ) {
		if( --s_nWriteDestAlphaTest <= 0 ) {
			s_bDestAlphaTest = FALSE;
		}
	}

	if( g_GameSettings & GAME_AUTORESET ) {
		s_nResolveCounts[s_nCurResolveIndex] = s_nResolved;
		s_nCurResolveIndex = (s_nCurResolveIndex+1)%ARRAYSIZE(s_nResolveCounts);

		int total = 0;
		for(int i = 0; i < ARRAYSIZE(s_nResolveCounts); ++i) total += s_nResolveCounts[i];

		if( total / ARRAYSIZE(s_nResolveCounts) > 3 ) {
			if( s_nLastResolveReset > (int)(fFPS * 8) ) {
				// reset
				DEBUG_LOG("ZeroGS: video mem reset\n");
				s_nLastResolveReset = 0;
				memset(s_nResolveCounts, 0, sizeof(s_nResolveCounts));

				s_RTs.ResolveAll();
				s_RTs.Destroy();
				s_DepthRTs.ResolveAll();
				s_DepthRTs.Destroy();

				vb[0].prndr = NULL; vb[0].pdepth = NULL; vb[0].bNeedFrameCheck = 1; vb[0].bNeedZCheck = 1;
				vb[1].prndr = NULL; vb[1].pdepth = NULL; vb[1].bNeedFrameCheck = 1; vb[1].bNeedZCheck = 1;
			}
		}

		s_nLastResolveReset++;
	}

	if( s_nResolved > 8 ) s_nResolved = 2;
	else if( s_nResolved > 0 ) --s_nResolved;

	if( g_nDepthUsed > 0 ) --g_nDepthUsed;

	s_ClutResolve = 0;
	s_PSM8Resolve = 0;
	g_nDepthUpdateCount = 0;

	maxmin = 608;
}

//////////////////////////
// Internal Definitions //
//////////////////////////

__forceinline void MOVZ(VertexGPU *p, u32 gsz, const VB& curvb) 
{
	p->z = curvb.zprimmask==0xffff?min((u32)0xffff, gsz):gsz;
}

__forceinline void MOVFOG(VertexGPU *p, Vertex gsf)
{
	p->f = ((s16)(gsf).f<<7)|0x7f;
}

__forceinline void SET_VERTEX(VertexGPU *p, int Index, const VB& curvb) 
{ 
	int index = Index; 
	
	p->x = (((int)gs.gsvertex[index].x - curvb.offset.x)>>1)&0xffff;
	p->y = (((int)gs.gsvertex[index].y - curvb.offset.y)>>1)&0xffff;
	
	/*x = ((int)gs.gsvertex[index].x - curvb.offset.x);
	y = ((int)gs.gsvertex[index].y - curvb.offset.y);
	p.x = (x&0x7fff) | (x < 0 ? 0x8000 : 0);
	p.y = (y&0x7fff) | (y < 0 ? 0x8000 : 0);*/
	
	p->f = ((s16)gs.gsvertex[index].f<<7)|0x7f;
	MOVZ(p, gs.gsvertex[index].z, curvb);
	p->rgba = prim->iip ? gs.gsvertex[index].rgba : gs.rgba;
	
	if ((g_GameSettings & GAME_TEXAHACK) && !(p->rgba&0xffffff))
		p->rgba = 0;
	if (prim->tme ) 
	{
		if( prim->fst ) 
		{
			p->s = (float)gs.gsvertex[index].u * fiTexWidth[prim->ctxt];
			p->t = (float)gs.gsvertex[index].v * fiTexHeight[prim->ctxt];
			p->q = 1;
		}
		else 
		{
			p->s = gs.gsvertex[index].s;
			p->t = gs.gsvertex[index].t;
			p->q = gs.gsvertex[index].q;
		}
	}
}

#define OUTPUT_VERT(fn, vert, id) { \
	fn("%c%d(%d): xyzf=(%4d,%4d,0x%x,%3d), rgba=0x%8.8x, stq = (%2.5f,%2.5f,%2.5f)\n", id==0?'*':' ', id, prim->prim, vert.x/8, vert.y/8, vert.z, vert.f/128, \
		vert.rgba, Clamp(vert.s, -10, 10), Clamp(vert.t, -10, 10), Clamp(vert.q, -10, 10)); \
} \


void ZeroGS::KickPoint()
{
	assert( gs.primC >= 1 );

	VB& curvb = vb[prim->ctxt];
	if (curvb.bNeedTexCheck) curvb.FlushTexData();

	if( !(g_GameSettings&GAME_DOPARALLELCTX) && vb[!prim->ctxt].dwCount > 0 && vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp )
	{
		assert( vb[prim->ctxt].dwCount == 0 );
		Flush(!prim->ctxt);
	}

	if( curvb.dwCount >= POINT_BUFFERFLUSH)
		Flush(prim->ctxt);

	curvb.Lock();
	int last = (gs.primIndex+2)%ARRAYSIZE(gs.gsvertex);

	VertexGPU* p = curvb.pbuf+curvb.dwCount;
	SET_VERTEX(&p[0], last, curvb);
	curvb.dwCount++;

#ifdef PRIM_LOG
	OUTPUT_VERT(PRIM_LOG, p[0], 0);
#endif
}

void ZeroGS::KickLine()
{
	assert( gs.primC >= 2 );
	VB& curvb = vb[prim->ctxt];
	if( curvb.bNeedTexCheck )
		curvb.FlushTexData();

	if( !(g_GameSettings&GAME_DOPARALLELCTX) && vb[!prim->ctxt].dwCount > 0 && vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp )
	{
		assert( vb[prim->ctxt].dwCount == 0 );
		Flush(!prim->ctxt);
	}

	if( curvb.dwCount >= POINT_BUFFERFLUSH/2 )
		Flush(prim->ctxt);

	curvb.Lock();
	int next = (gs.primIndex+1)%ARRAYSIZE(gs.gsvertex);
	int last = (gs.primIndex+2)%ARRAYSIZE(gs.gsvertex);

	VertexGPU* p = curvb.pbuf+curvb.dwCount*2;
	SET_VERTEX(&p[0], next, curvb);
	SET_VERTEX(&p[1], last, curvb);

	curvb.dwCount++;

#ifdef PRIM_LOG
	OUTPUT_VERT(PRIM_LOG, p[0], 0);
	OUTPUT_VERT(PRIM_LOG, p[1], 1);
#endif
}

void ZeroGS::KickTriangle()
{
	assert( gs.primC >= 3 );
	VB& curvb = vb[prim->ctxt];
	if (curvb.bNeedTexCheck) curvb.FlushTexData();

	if( !(g_GameSettings&GAME_DOPARALLELCTX) && vb[!prim->ctxt].dwCount > 0 && vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp )
	{
		assert( vb[prim->ctxt].dwCount == 0 );
		Flush(!prim->ctxt);
	}

	if( curvb.dwCount >= POINT_BUFFERFLUSH/3 )
		Flush(prim->ctxt);

	curvb.Lock();
	VertexGPU* p = curvb.pbuf+curvb.dwCount*3;
	SET_VERTEX(&p[0], 0, curvb);
	SET_VERTEX(&p[1], 1, curvb);
	SET_VERTEX(&p[2], 2, curvb);

	curvb.dwCount++;

#ifdef PRIM_LOG
	OUTPUT_VERT(PRIM_LOG, p[0], 0);
	OUTPUT_VERT(PRIM_LOG, p[1], 1);
	OUTPUT_VERT(PRIM_LOG, p[2], 2);
#endif
}

void ZeroGS::KickTriangleFan()
{
	assert( gs.primC >= 3 );
	VB& curvb = vb[prim->ctxt];
	if (curvb.bNeedTexCheck) curvb.FlushTexData();

	if( !(g_GameSettings&GAME_DOPARALLELCTX) && vb[!prim->ctxt].dwCount > 0 && vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp )
	{
		assert( vb[prim->ctxt].dwCount == 0 );
		Flush(!prim->ctxt);
	}

	if( curvb.dwCount >= POINT_BUFFERFLUSH/3 )
		Flush(prim->ctxt);

	curvb.Lock();
	VertexGPU* p = curvb.pbuf+curvb.dwCount*3;
	SET_VERTEX(&p[0], 0, curvb);
	SET_VERTEX(&p[1], 1, curvb);
	SET_VERTEX(&p[2], 2, curvb);

	curvb.dwCount++;

	// add 1 to skip the first vertex
	if( gs.primIndex == gs.nTriFanVert )
		gs.primIndex = (gs.primIndex+1)%ARRAYSIZE(gs.gsvertex);

#ifdef PRIM_LOG
	OUTPUT_VERT(PRIM_LOG, p[0], 0);
	OUTPUT_VERT(PRIM_LOG, p[1], 1);
	OUTPUT_VERT(PRIM_LOG, p[2], 2);
#endif
}

__forceinline void SetKickVertex(VertexGPU *p, Vertex v, int next, const VB& curvb) 
{
	SET_VERTEX(p, next, curvb); 
	MOVZ(p, v.z, curvb);	
	MOVFOG(p, v);
}

void ZeroGS::KickSprite()
{
	assert( gs.primC >= 2 );
	VB& curvb = vb[prim->ctxt];
	if( curvb.bNeedTexCheck )
		curvb.FlushTexData();

	if( !(g_GameSettings&GAME_DOPARALLELCTX) && vb[!prim->ctxt].dwCount > 0 && vb[prim->ctxt].gsfb.fbp == vb[!prim->ctxt].gsfb.fbp )
	{
		assert( vb[prim->ctxt].dwCount == 0 );
		Flush(!prim->ctxt);
	}

	if (curvb.dwCount >= POINT_BUFFERFLUSH/3) Flush(prim->ctxt);

	curvb.Lock();
	int next = (gs.primIndex+1)%ARRAYSIZE(gs.gsvertex);
	int last = (gs.primIndex+2)%ARRAYSIZE(gs.gsvertex);

	// sprite is too small and AA shows lines (tek4)
	if( s_AAx ) 
	{
		gs.gsvertex[last].x += 4;
		if (s_AAy) gs.gsvertex[last].y += 4;
	}

	// might be bad sprite (KH dialog text)
	//if( gs.gsvertex[next].x == gs.gsvertex[last].x || gs.gsvertex[next].y == gs.gsvertex[last].y )
	//	return;

	VertexGPU* p = curvb.pbuf+curvb.dwCount*3;
	
	SetKickVertex(&p[0], gs.gsvertex[last], next, curvb);
	SetKickVertex(&p[3], gs.gsvertex[last], next, curvb);
	SetKickVertex(&p[1], gs.gsvertex[last], last, curvb);
	SetKickVertex(&p[4], gs.gsvertex[last], last, curvb);

	if (g_MaxRenderedHeight < p[0].y) g_MaxRenderedHeight = p[0].y;
	if (g_MaxRenderedHeight < p[1].y) g_MaxRenderedHeight = p[1].y;

	SetKickVertex(&p[2], gs.gsvertex[last], next, curvb);
	p[2].s = p[1].s;
	p[2].x = p[1].x;

	SetKickVertex(&p[5], gs.gsvertex[last], last, curvb);
	p[5].s = p[0].s;
	p[5].x = p[0].x;	

	curvb.dwCount += 2;

#ifdef PRIM_LOG
	OUTPUT_VERT(PRIM_LOG, p[0], 0);
	OUTPUT_VERT(PRIM_LOG, p[1], 1);
#endif
}

void ZeroGS::KickDummy()
{
	//GREG_LOG("Kicking bad primitive: %.8x\n", *(u32*)prim);
}

__forceinline void ZeroGS::RenderFBA(const VB& curvb)
{
	// add fba to all pixels
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_CLEAR);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFAIL, D3DSTENCILOP_ZERO);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
	SETRS(D3DRS_STENCILREF, STENCIL_FBA);

	SETRS(D3DRS_ZENABLE, FALSE);
	SETRS(D3DRS_ZWRITEENABLE, FALSE);
	SETRS(D3DRS_COLORWRITEENABLE, 0);

	if( s_bWriteDepth ) {
		if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
		else pd3dDevice->SetRenderTarget(1, NULL);
	}

	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_ALPHATESTENABLE, TRUE);
	SETRS(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
	SETRS(D3DRS_ALPHAREF, 0xff);

	DXVEC4 v;
	v.x = 1; v.y = 2; v.z = 0; v.w = 0;
	SETCONSTF(GPU_ONECOLOR, v);

	DRAW();

	if( !curvb.test.ate )
		SETRS(D3DRS_ALPHATESTENABLE, FALSE);
	else {
		SETRS(D3DRS_ALPHAFUNC, g_dwAlphaCmp[curvb.test.atst]);
		SETRS(D3DRS_ALPHAREF, b2XAlphaTest ? min(255,2 * curvb.test.aref) : curvb.test.aref);
	}

	// reset (not necessary)
	SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);
	SETRS(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);

	if( !curvb.zbuf.zmsk ) {
		SETRS(D3DRS_ZWRITEENABLE, TRUE);
		
		assert( curvb.pdepth != NULL );
		if( s_bWriteDepth ) {
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
			else pd3dDevice->SetRenderTarget(1, curvb.pdepth->psurf);
		}
	}
	SETRS(D3DRS_ZENABLE, curvb.test.zte);
}

__forceinline void ZeroGS::RenderAlphaTest(const VB& curvb)
{
	if( !g_bUpdateStencil ) return;

	if( curvb.test.ate ) {
		if( curvb.test.afail == 1 ) SETRS(D3DRS_ALPHATESTENABLE, FALSE);
	}

	SETRS(D3DRS_ZWRITEENABLE, FALSE);
	SETRS(D3DRS_COLORWRITEENABLE, 0);

	DXVEC4 v;
	v.x = 1; v.y = 2; v.z = 0; v.w = 0;
	SETCONSTF(GPU_ONECOLOR, v);

	if( s_bWriteDepth ) {
		if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
		else pd3dDevice->SetRenderTarget(1,NULL);
	}

	// or a 1 to the stencil buffer wherever alpha passes
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);

	SETRS(D3DRS_STENCILENABLE, TRUE);
	
	if( !s_bDestAlphaTest ) {
		// clear everything
		SETRS(D3DRS_STENCILREF, 0);
		SETRS(D3DRS_STENCILWRITEMASK, STENCIL_CLEAR);
		SETRS(D3DRS_ALPHATESTENABLE, FALSE);
		DRAW();

		if( curvb.test.ate && curvb.test.afail != 1 && USEALPHATESTING)
			SETRS(D3DRS_ALPHATESTENABLE, TRUE);
	}

	if( curvb.test.ate && curvb.test.atst>1 && curvb.test.aref > 0x80) {
		v.x = 1; v.y = 1; v.z = 0; v.w = 0;
		SETCONSTF(GPU_ONECOLOR, v);
		SETRS(D3DRS_ALPHAREF, curvb.test.aref);
	}

	SETRS(D3DRS_STENCILREF, STENCIL_SPECIAL);
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_SPECIAL);
	SETRS(D3DRS_ZENABLE, FALSE);
	
	DRAW();

	if( curvb.test.zte ) SETRS(D3DRS_ZENABLE, TRUE);
	SETRS(D3DRS_ALPHATESTENABLE, 0);
	SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);

	if( !curvb.zbuf.zmsk ) {
		SETRS(D3DRS_ZWRITEENABLE, TRUE);

		// set rt next level
		if( s_bWriteDepth ) {
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
			else pd3dDevice->SetRenderTarget(1, curvb.pdepth->psurf);
		}
	}
}

__forceinline void ZeroGS::RenderStencil(const VB& curvb, DWORD dwUsingSpecialTesting)
{
	//NOTE: This stencil hack for dest alpha testing ONLY works when 
	// the geometry in one DrawPrimitive call does not overlap

	// mark the stencil buffer for the new data's bits (mark 4 if alpha is >= 0xff)
	// mark 4 if a pixel was written (so that the stencil buf can be changed with new values)
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_PIXELWRITE);

	dwStencilMask = (curvb.test.date?STENCIL_ALPHABIT:0)|(dwUsingSpecialTesting?STENCIL_SPECIAL:0);
	SETRS(D3DRS_STENCILMASK, dwStencilMask);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFUNC, dwStencilMask ? D3DCMP_EQUAL : D3DCMP_ALWAYS);

	dwStencilRef = curvb.test.date*curvb.test.datm|STENCIL_PIXELWRITE|(dwUsingSpecialTesting?STENCIL_SPECIAL:0);
	SETRS(D3DRS_STENCILREF, dwStencilRef);
}

__forceinline void ZeroGS::ProcessStencil(const VB& curvb)
{
	assert( !curvb.fba.fba );

	// set new alpha bit
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_ALPHABIT);
	SETRS(D3DRS_STENCILMASK, STENCIL_PIXELWRITE|STENCIL_FBA);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
	SETRS(D3DRS_STENCILREF, STENCIL_PIXELWRITE);

	SETRS(D3DRS_ZENABLE, FALSE);
	SETRS(D3DRS_ZWRITEENABLE, FALSE);
	SETRS(D3DRS_COLORWRITEENABLE, 0);

	if( s_bWriteDepth ) {
		if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
		else pd3dDevice->SetRenderTarget(1, NULL);
	}

	SETRS(D3DRS_ALPHATESTENABLE, 0);

	pd3dDevice->SetPixelShader(ppsOne);
	DRAW();

	// process when alpha >= 0xff
	SETRS(D3DRS_STENCILREF, STENCIL_PIXELWRITE|STENCIL_FBA|STENCIL_ALPHABIT);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
	DRAW();

	// clear STENCIL_PIXELWRITE bit
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_CLEAR);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
	SETRS(D3DRS_STENCILREF, 0);

	DRAW();

	// restore state
	SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);

	if( curvb.test.ate && USEALPHATESTING)
		SETRS(D3DRS_ALPHATESTENABLE, TRUE);

	if( !curvb.zbuf.zmsk ) {
		SETRS(D3DRS_ZWRITEENABLE, TRUE);

		if( s_bWriteDepth ) {
			assert( curvb.pdepth != NULL );
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
			else pd3dDevice->SetRenderTarget(1, curvb.pdepth->psurf);
		}
	}

	SETRS(D3DRS_ZENABLE, curvb.test.zte);

	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
}

__forceinline void ZeroGS::ProcessFBA(const VB& curvb)
{
	if( (curvb.frame.fbm&0x80000000) ) return;

	// add fba to all pixels that were written and alpha was less than 0xff
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_ALPHABIT);
	SETRS(D3DRS_STENCILMASK, STENCIL_PIXELWRITE|STENCIL_FBA);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
	SETRS(D3DRS_STENCILREF, STENCIL_FBA|STENCIL_PIXELWRITE|STENCIL_ALPHABIT);

	SETRS(D3DRS_ZENABLE, FALSE);
	SETRS(D3DRS_ZWRITEENABLE, FALSE);
	SETRS(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA);
	
	if( s_bWriteDepth ) {
		if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0);
		else pd3dDevice->SetRenderTarget(1, NULL);
	}

	// processes the pixels with ALPHA < 0x80*2
	SETRS(D3DRS_ALPHATESTENABLE, TRUE);
	SETRS(D3DRS_ALPHAFUNC, D3DCMP_LESSEQUAL);
	SETRS(D3DRS_ALPHAREF, 0xff);

	// add 1 to dest
	SETRS(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
	SETRS(D3DRS_DESTBLENDALPHA, D3DBLEND_ONE);
	SETRS(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);

	float f = 1;
	SETCONSTF(GPU_ONECOLOR, &f);
	pd3dDevice->SetPixelShader(ppsOne);

	DRAW();

	SETRS(D3DRS_ALPHATESTENABLE, FALSE);

	// reset bits
	SETRS(D3DRS_STENCILWRITEMASK, STENCIL_CLEAR);
	SETRS(D3DRS_STENCILMASK, STENCIL_PIXELWRITE|STENCIL_FBA);
	SETRS(D3DRS_STENCILPASS, D3DSTENCILOP_ZERO);
	SETRS(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
	SETRS(D3DRS_STENCILFUNC, D3DCMP_GREATER);
	SETRS(D3DRS_STENCILREF, 0);

	DRAW();

	if( curvb.test.atst && USEALPHATESTING) {
		SETRS(D3DRS_ALPHATESTENABLE, TRUE);
		SETRS(D3DRS_ALPHAFUNC, g_dwAlphaCmp[curvb.test.atst]);
		SETRS(D3DRS_ALPHAREF, b2XAlphaTest ? min(255,2 * curvb.test.aref) : curvb.test.aref);
	}

	// restore (SetAlphaVariables)
	if( !bNeedAlphaColor ) SETRS(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);//(bNeedBlendFactorInAlpha ? D3DBLEND_ZERO : D3DBLEND_ONE));
	SETRS(D3DRS_DESTBLENDALPHA, D3DBLEND_ZERO);//bNeedBlendFactorInAlpha ? D3DBLEND_ONE : D3DBLEND_ZERO);
	if(bNeedAlphaColor && vAlphaBlendColor.y<0) SETRS(D3DRS_BLENDOPALPHA, D3DBLENDOP_REVSUBTRACT);

	// reset (not necessary)
	SETRS(D3DRS_COLORWRITEENABLE, s_dwColorWrite);

	if( !curvb.zbuf.zmsk ) {
		SETRS(D3DRS_ZWRITEENABLE, TRUE);

		if( s_bWriteDepth ) {
			if( bIndepWriteMasks ) SETRS(D3DRS_COLORWRITEENABLE1, 0xf);
			else pd3dDevice->SetRenderTarget(1, curvb.pdepth->psurf);
		}
	}
	SETRS(D3DRS_ZENABLE, curvb.test.zte);
}

inline void ZeroGS::SetContextTarget(int context)
{
	VB& curvb = vb[context];

	if( curvb.prndr == NULL )
		curvb.prndr = s_RTs.GetTarg(curvb.frame, 0, GET_MAXHEIGHT(curvb.gsfb.fbp, curvb.gsfb.fbw, curvb.gsfb.psm));

	// make sure targets are valid
	if( curvb.pdepth == NULL ) {
		frameInfo f;
		f.fbp = curvb.zbuf.zbp;
		f.fbw = curvb.frame.fbw;
		f.fbh = curvb.prndr->fbh;
		f.psm = curvb.zbuf.psm;
		f.fbm = 0;
		curvb.pdepth = (CDepthTarget*)s_DepthRTs.GetTarg(f, CRenderTargetMngr::TO_DepthBuffer|CRenderTargetMngr::TO_StrictHeight|
			(curvb.zbuf.zmsk?CRenderTargetMngr::TO_Virtual:0), GET_MAXHEIGHT(curvb.zbuf.zbp, curvb.gsfb.fbw, 0));
	}
	
	assert( curvb.prndr != NULL && curvb.pdepth != NULL );
	assert( curvb.pdepth->fbh == curvb.prndr->targheight );
	
//	if( curvb.pdepth->fbh != curvb.prndr->fbh ) {
//
//		s_DepthRTs.DestroyTarg(curvb.pdepth);
//		ERROR_LOG("ZeroGS: render and depth heights different: %x %x\n", curvb.prndr->fbh, curvb.pdepth->fbh);
//		frameInfo f;
//		f.fbp = curvb.zbuf.zbp;
//		f.fbw = curvb.frame.fbw;
//		f.fbh = curvb.prndr->fbh;
//		f.psm = curvb.zbuf.psm;
//		f.fbm = 0;
//		curvb.pdepth = (CDepthTarget*)s_DepthRTs.GetTarg(f, CRenderTargetMngr::TO_DepthBuffer|CRenderTargetMngr::TO_StrictHeight|
//			(curvb.zbuf.zmsk?CRenderTargetMngr::TO_Virtual:0), GET_MAXHEIGHT(curvb.zbuf.zbp, curvb.gsfb.fbw, 0));		
//	}

	if( curvb.pdepth->status & CRenderTarget::TS_Virtual) {
		
		if( !curvb.zbuf.zmsk ) {
			CRenderTarget* ptemp = s_DepthRTs.Promote(curvb.pdepth->fbp|(curvb.pdepth->fbw<<16));
			assert( ptemp == curvb.pdepth );
		}
		else
			curvb.pdepth->status &= ~CRenderTarget::TS_NeedUpdate;
	}

	BOOL bSetTarg = 1;
	if( curvb.pdepth->status & CRenderTarget::TS_NeedUpdate ) {

		assert( !(curvb.pdepth->status & CRenderTarget::TS_Virtual) );

		// don't update if virtual
		curvb.pdepth->Update(context, curvb.prndr);
		bSetTarg = 0;
	}
	
	if( curvb.prndr->status & CRenderTarget::TS_NeedUpdate ) {

		if( s_bWriteDepth ) {
			if( bSetTarg ) {
				pd3dDevice->SetRenderTarget(1, curvb.pdepth->psurf);
				pd3dDevice->SetDepthStencilSurface(curvb.pdepth->pdepth);
			}
		}
		else if( bSetTarg )
			pd3dDevice->SetDepthStencilSurface(curvb.pdepth->pdepth);

		curvb.prndr->Update(context, curvb.pdepth);
		// note, targ already set
	}
	else { 

		//if( (vb[0].prndr != vb[1].prndr && vb[!context].bVarsSetTarg) || !vb[context].bVarsSetTarg )
			pd3dDevice->SetRenderTarget(0, curvb.prndr->psurf);
		//if( bSetTarg && ((vb[0].pdepth != vb[1].pdepth && vb[!context].bVarsSetTarg) || !vb[context].bVarsSetTarg) ) 
			curvb.pdepth->SetDepthTarget();

		if( s_ptexCurSet[0] == curvb.prndr->ptex ) {
			s_ptexCurSet[0] = NULL;
			pd3dDevice->SetTexture(SAMP_MEMORY0, NULL);
		}
		if( s_ptexCurSet[1] == curvb.prndr->ptex ) {
			s_ptexCurSet[1] = NULL;
			pd3dDevice->SetTexture(SAMP_MEMORY1, NULL);
		}

		curvb.prndr->SetViewport();
	}

	curvb.prndr->SetTarget(curvb.frame.fbp, curvb.scissor, context);

	if( (curvb.zbuf.zbp-curvb.pdepth->fbp) != (curvb.frame.fbp - curvb.prndr->fbp) && curvb.test.zte ) {
		WARN_LOG("frame and zbuf not aligned\n");
	}

	curvb.bVarsSetTarg = TRUE;
	if( vb[!context].prndr != curvb.prndr )
		vb[!context].bVarsSetTarg = FALSE;

	assert( !(curvb.prndr->status&CRenderTarget::TS_NeedUpdate) );
	assert( curvb.pdepth == NULL || !(curvb.pdepth->status&CRenderTarget::TS_NeedUpdate) );
}

void ZeroGS::SetTexVariables(int context)
{
	if( !vb[context].curprim.tme ) {
		return;
	}

	assert( !vb[context].bNeedTexCheck );

	DXVEC4 v, v2;
	tex0Info& tex0 = vb[context].tex0;
	
	float fw = (float)tex0.tw;
	float fh = (float)tex0.th;

	if( !vb[context].bTexConstsSync ) {
		// alpha and texture highlighting
		DXVEC4 valpha, valpha2;

		// if clut, use the frame format
		int psm = tex0.psm;
		if( PSMT_ISCLUT(tex0.psm) ) psm = tex0.cpsm;
		
		int nNeedAlpha = (psm == 1 || psm == 2 || psm == 10);

		DXVEC4 vblack;
		vblack.x = vblack.y = vblack.z = vblack.w = 10;

		switch(tex0.tfx) {
			case 0:
				valpha.z = 0; valpha.w = 0;
				valpha2.x = 0; valpha2.y = 0;
				valpha2.z = 2; valpha2.w = 1;
				
				break;
			case 1:
				valpha.z = 0; valpha.w = 1;
				valpha2.x = 1; valpha2.y = 0;
				valpha2.z = 0; valpha2.w = 0;
				
				break;
			case 2:
				valpha.z = 1; valpha.w = 1.0f;
				valpha2.x = 0; valpha2.y = tex0.tcc ? 1.0f : 0.0f;
				valpha2.z = 2; valpha2.w = 0;				
				
				break;

			case 3:
				valpha.z = 1; valpha.w = tex0.tcc ? 0.0f : 1.0f;
				valpha2.x = 0; valpha2.y = tex0.tcc ? 1.0f : 0.0f;
				valpha2.z = 2; valpha2.w = 0;
		
				break;
			default: __assume(0);
		}

		if( tex0.tcc ) {

			if( tex0.tfx == 1 ) {
				//mode.x = 10;
				valpha.z = 0; valpha.w = 0;
				valpha2.x = 1; valpha2.y = 1;
				valpha2.z = 0; valpha2.w = 0;
			}

			if( nNeedAlpha ) {
				
				if( tex0.tfx == 0 ) {
					// make sure alpha is mult by two when the output is Cv = Ct*Cf
					valpha.x = 2*gs.texa.fta[0];
					// if 24bit, always choose ta[0]
					valpha.y = 2*gs.texa.fta[psm != 1];
					valpha.y -= valpha.x;
				}
				else {
					valpha.x = gs.texa.fta[0];
					// if 24bit, always choose ta[0]
					valpha.y = gs.texa.fta[psm != 1];
					valpha.y -= valpha.x;
				}

				// need black detection
				if( gs.texa.aem && psm == PSMCT24 )
					vblack.w = 0;
			}
			else {
				if( tex0.tfx == 0 ) {
					valpha.x = 0;
					valpha.y = 2;
				}
				else {
					valpha.x = 0;
					valpha.y = 1;
				}
			}
		}
		else {

			// reset alpha to color
			valpha.x = valpha.y = 0;
			valpha.w = 1;
		}

		SETCONSTF(GPU_TEXALPHA0+context, &valpha);
		SETCONSTF(GPU_TEXALPHA20+context, &valpha2);
		if( tex0.tcc && gs.texa.aem && (psm == PSMCT24 || psm == PSMCT16 || psm == PSMCT16S) )
			SETCONSTF(GPU_TESTBLACK0+context, &vblack);

		// clamp relies on texture width
		{
			clampInfo* pclamp = &ZeroGS::vb[context].clamp;
			DXVEC4 v, v2;
			v.x = v.y = 0;
			LPD3DTEX* ptex = ZeroGS::vb[context].ptexClamp;
			ptex[0] = ptex[1] = NULL;

			float fw = ZeroGS::vb[context].tex0.tw;
			float fh = ZeroGS::vb[context].tex0.th;

			switch(pclamp->wms) {
				case 0:
					v2.x = -1e10;	v2.z = 1e10;
					break;
				case 1: // pclamp
					// suikoden5 movie text
					v2.x = 0; v2.z = 1-0.5f/fw;
					break;
				case 2: // reg pclamp
					v2.x = (pclamp->minu+0.5f)/fw;	v2.z = (pclamp->maxu-0.5f)/fw;
					break;

				case 3:	// region rep x
					v.x = 0.9999f;
					v.z = fw / (float)GPU_TEXMASKWIDTH;
					v2.x = (float)GPU_TEXMASKWIDTH / fw;
					v2.z = pclamp->maxu / fw;

					if( pclamp->minu != g_PrevBitwiseTexX ) {
						g_PrevBitwiseTexX = pclamp->minu;
						ptex[0] = ZeroGS::s_BitwiseTextures.GetTex(pclamp->minu, NULL);
					}
					break;

				default: __assume(0);
			}

			switch(pclamp->wmt) {
				case 0:
					v2.y = -1e10;	v2.w = 1e10;
					break;
				case 1: // pclamp
					// suikoden5 movie text
					v2.y = 0;	v2.w = 1-0.5f/fh;
					break;
				case 2: // reg pclamp
					v2.y = (pclamp->minv+0.5f)/fh; v2.w = (pclamp->maxv-0.5f)/fh;
					break;

				case 3:	// region rep y
					v.y = 0.9999f;
					v.w = fh / (float)GPU_TEXMASKWIDTH;
					v2.y = (float)GPU_TEXMASKWIDTH / fh;
					v2.w = pclamp->maxv / fh;

					if( pclamp->minv != g_PrevBitwiseTexY ) {
						g_PrevBitwiseTexY = pclamp->minv;
						ptex[1] = ZeroGS::s_BitwiseTextures.GetTex(pclamp->minv, ptex[0]);
					}
					break;

				default: __assume(0);
			}

			SETCONSTF(GPU_TEXWRAPMODE0+context, v);
			SETCONSTF(GPU_CLAMPEXTS0+context, v2);
		}

		vb[context].bTexConstsSync = TRUE;
	}

	if(s_bTexFlush ) {
		if( PSMT_ISCLUT(tex0.psm) )
			texClutWrite(context);
		else
			s_bTexFlush = FALSE;
	}
}

void ZeroGS::SetTexVariablesInt(int context, int bilinear, const tex0Info& tex0, CMemoryTarget* pmemtarg, int force)
{
	DXVEC4 v;
	assert( pmemtarg != NULL );

	float fw = (float)tex0.tw;
	float fh = (float)tex0.th;

	if( bilinear > 1 || (bilinear && conf.bilinear)) {
		v.x = (float)fw;
		v.y = (float)fh;
		v.z = 1.0f / (float)fw;
		v.w = 1.0f / (float)fh;
		SETCONSTF(GPU_REALTEXDIMS0+context, v);
	}

	if( m_Blocks[tex0.psm].bpp == 0 ) {
		ERROR_LOG("ZeroGS: Undefined tex psm 0x%x!\n", tex0.psm);
		return;
	}

	const BLOCK& b = m_Blocks[tex0.psm];

	float fbw = (float)tex0.tbw;

	v.x = b.vTexDims.x * fw;
	v.y = b.vTexDims.y * fh;
	v.z = (float)BLOCK_TEXWIDTH*(0.002f / 64.0f + 0.01f/128.0f);
	v.w = (float)BLOCK_TEXHEIGHT*0.2f/512.0f;

	if( bilinear > 1 || (conf.bilinear && bilinear) ) {
		v.x *= 1/128.0f;
		v.y *= 1/512.0f;
		v.z *= 1/128.0f;
		v.w *= 1/512.0f;
	}
	SETCONSTF(GPU_TEXDIMS0+context, v);

	float g_fitexwidth = g_fiGPU_TEXWIDTH/(float)pmemtarg->widthmult;
	float g_texwidth = GPU_TEXWIDTH*(float)pmemtarg->widthmult;

	SETCONSTF(GPU_TEXBLOCK0+context, &b.vTexBlock.x);

	float fpage = tex0.tbp0*(64.0f*g_fitexwidth) + 0.05f * g_fitexwidth;
	float fpageint = floorf(fpage);
	int starttbp = (int)fpage;
	
	// 2048 is number of words to span one page
	float fblockstride = (2048.0f /(float)(g_texwidth*BLOCK_TEXWIDTH)) * b.vTexDims.x * fbw;
	assert( fblockstride >= 1.0f );

	v.x = (float)(2048 * g_fitexwidth);
	v.y = fblockstride;
	v.z = g_fBlockMult/(float)pmemtarg->widthmult;
	v.w = fpage-fpageint;

	if( g_fBlockMult > 1 ) {
		// make sure to divide by mult (since the G16R16 texture loses info)
		v.z *= b.bpp * (1/32.0f);
	}

	SETCONSTF(GPU_TEXOFFSET0+context, v);

	v.y = (float)1.0f / (float)((pmemtarg->realheight+pmemtarg->widthmult-1)/pmemtarg->widthmult);
	v.x = (fpageint-(float)pmemtarg->realy/(float)pmemtarg->widthmult+0.5f)*v.y;

	SETCONSTF(GPU_PAGEOFFSET0+context, v);

	if( force ) {
		pd3dDevice->SetTexture(SAMP_MEMORY0+context, pmemtarg->ptex);
		s_ptexCurSet[context] = pmemtarg->ptex;
	}
	else s_ptexNextSet[context] = pmemtarg->ptex;
	vb[context].pmemtarg = pmemtarg;

	vb[context].bVarsTexSync = FALSE;
}

// assumes texture factor is unused
#define SET_ALPHA_COLOR_FACTOR(sign) { \
	switch(a.c) { \
		case 0: \
			bNeedAlphaColor = 1; \
			vAlphaBlendColor.y = (sign) ? 2.0f*255.0f/256.0f : -2.0f*255.0f/256.0f; \
			SETRS(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE); \
			SETRS(D3DRS_BLENDOPALPHA, (sign) ? D3DBLENDOP_ADD : D3DBLENDOP_REVSUBTRACT); \
			break; \
		case 1: \
			/* if in 24 bit mode, dest alpha should be one */ \
			switch(vb[icurctx].prndr->psm&0xf) { \
				case 0: \
					bDestAlphaColor = (a.d!=2)&&((a.a==a.d)||(a.b==a.d)); \
					break; \
				case 1: \
					/* dest alpha should be one */ \
					bDestAlphaColor = 2; \
					break; \
				/* default: 16bit surface, so returned alpha is ok */ \
			} \
			break; \
			\
		case 2: \
			bNeedBlendFactorInAlpha = 1; /* should disable alpha channel writing */ \
			bNeedAlphaColor = 1; \
			vAlphaBlendColor.y = 0; \
			vAlphaBlendColor.w = (sign) ? (float)a.fix * (2.0f/255.0f) : (float)a.fix * (-2.0f/255.0f); \
			usec = 0; /* change so that alpha comes from source*/ \
			break; \
	} \
} \

//if( a.fix <= 0x80 ) { \
//				dwTemp = (a.fix*2)>255?255:(a.fix*2); \
//				dwTemp = dwTemp|(dwTemp<<8)|(dwTemp<<16)|0x80000000; \
//				DEBUG_LOG("bfactor: %8.8x\n", dwTemp); \
//				SETRS(D3DRS_BLENDFACTOR, dwTemp); \
//			} \
//			else { \

void ZeroGS::ResetAlphaVariables()
{
	s_bAlphaSet = FALSE;
}

void ZeroGS::SetAlphaVariables(const alphaInfo& a)
{
	SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING); // always set

	if( s_bAlphaSet && a.abcd == s_alphaInfo.abcd && a.fix == s_alphaInfo.fix ) {
		return;
	}

	// TODO: negative color when not clamping turns to positive???
	g_vars._bAlphaState = 0; // set all to zero
	bNeedBlendFactorInAlpha = 0;
	b2XAlphaTest = 1;
	DWORD dwTemp = 0xffffffff;

	// default
	SETRS(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
	SETRS(D3DRS_DESTBLENDALPHA, D3DBLEND_ZERO);
	SETRS(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);

	s_alphaInfo = a;

	vAlphaBlendColor = DXVEC4(1,2*255.0f/256.0f,0,0);
	DWORD usec = a.c;

	if( a.a == a.b ) { // just d remains
		SETRS(D3DRS_ALPHABLENDENABLE, USEALPHABLENDING);

		if( a.d == 0 ) {
			SETRS(D3DRS_ALPHABLENDENABLE, 0);
		}
		else {
			SETRS(D3DRS_DESTBLEND, a.d == 1 ? D3DBLEND_ONE : D3DBLEND_ZERO);
			SETRS(D3DRS_SRCBLEND, D3DBLEND_ZERO);
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
		}

		goto EndSetAlpha;
	}
	else if( a.d == 2 ) { // zero

		if( a.a == 2 ) {
			// zero all color
			SETRS(D3DRS_SRCBLEND, D3DBLEND_ZERO);
			SETRS(D3DRS_DESTBLEND, D3DBLEND_ZERO);	
			goto EndSetAlpha;
		}
		else if( a.b == 2 ) {				
			//b2XAlphaTest = 1;
			
			SET_ALPHA_COLOR_FACTOR(1);

			if( bDestAlphaColor == 2 ) {
				SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? D3DBLEND_ONE : D3DBLEND_ZERO);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? D3DBLEND_ZERO : D3DBLEND_ONE);
			}
			else {
				if( bNeedAlphaColor ) bAlphaClamping = 2;

				SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);					
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? blendalpha[usec] : D3DBLEND_ZERO);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? D3DBLEND_ZERO : blendalpha[usec]);
			}

			goto EndSetAlpha;
		}

		// nothing is zero, so must do some real blending
		//b2XAlphaTest = 1;
		bAlphaClamping = 3;

		SET_ALPHA_COLOR_FACTOR(1);

		SETRS(D3DRS_BLENDOP, a.a == 0 ? D3DBLENDOP_SUBTRACT : D3DBLENDOP_REVSUBTRACT);
		SETRS(D3DRS_SRCBLEND, bDestAlphaColor == 2 ? D3DBLEND_ONE : blendalpha[usec]);
		SETRS(D3DRS_DESTBLEND, bDestAlphaColor == 2 ? D3DBLEND_ONE : blendalpha[usec]);
	}
	else if( a.a == 2 ) {	// zero

		//b2XAlphaTest = 1;
		bAlphaClamping = 1; // min testing

		SET_ALPHA_COLOR_FACTOR(1);

		if( a.b == a.d ) {
			// can get away with 1-A
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
			SETRS(D3DRS_SRCBLEND, (a.b == 0 && bDestAlphaColor != 2) ? blendinvalpha[usec] : D3DBLEND_ZERO);
			SETRS(D3DRS_DESTBLEND, (a.b == 0 || bDestAlphaColor == 2) ? D3DBLEND_ZERO : blendinvalpha[usec]);
		}
		else {
			SETRS(D3DRS_BLENDOP, a.b==0 ? D3DBLENDOP_REVSUBTRACT : D3DBLENDOP_SUBTRACT);
			SETRS(D3DRS_SRCBLEND, (a.b == 0 && bDestAlphaColor != 2) ? blendalpha[usec] : D3DBLEND_ONE);
			SETRS(D3DRS_DESTBLEND, (a.b == 0 || bDestAlphaColor == 2 ) ? D3DBLEND_ONE : blendalpha[usec]);
		}
	}
	else if( a.b == 2 ) {
		bAlphaClamping = 2; // max testing

		SET_ALPHA_COLOR_FACTOR(a.a!=a.d);

		if( a.a == a.d ) {
			// can get away with 1+A, but need to set alpha to negative
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);

			if( bDestAlphaColor == 2 ) {
				assert(usec==1);

				// all ones
				bNeedAlphaColor = 1;
				bNeedBlendFactorInAlpha = 1;
				vAlphaBlendColor.y = 0;
				vAlphaBlendColor.w = -1;
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? D3DBLEND_INVSRCALPHA : D3DBLEND_ZERO);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? D3DBLEND_ZERO : D3DBLEND_INVSRCALPHA);
			}
			else {
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? blendinvalpha[usec] : D3DBLEND_ZERO);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? D3DBLEND_ZERO : blendinvalpha[usec]);
			}
		}
		else {
			//b2XAlphaTest = 1;
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
			SETRS(D3DRS_SRCBLEND, (a.a == 0 && bDestAlphaColor != 2) ? blendalpha[usec] : D3DBLEND_ONE);
			SETRS(D3DRS_DESTBLEND, (a.a == 0 || bDestAlphaColor == 2) ? D3DBLEND_ONE : blendalpha[usec]);
		}
	}
	else {
		// all 3 components are valid!
		bAlphaClamping = 3; // all testing
		SET_ALPHA_COLOR_FACTOR(a.a!=a.d);

		if( a.a == a.d ) {
			// can get away with 1+A, but need to set alpha to negative
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);
			
			if( bDestAlphaColor == 2 ) {
				assert(usec==1);

				// all ones
				bNeedAlphaColor = 1;
				bNeedBlendFactorInAlpha = 1;
				vAlphaBlendColor.y = 0;
				vAlphaBlendColor.w = -1;
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? D3DBLEND_INVSRCALPHA : D3DBLEND_SRCALPHA);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? D3DBLEND_SRCALPHA : D3DBLEND_INVSRCALPHA);
			}
			else {
				SETRS(D3DRS_SRCBLEND, a.a == 0 ? blendinvalpha[usec] : blendalpha[usec]);
				SETRS(D3DRS_DESTBLEND, a.a == 0 ? blendalpha[usec] : blendinvalpha[usec]);
			}
		}
		else {
			assert(a.b == a.d);
			SETRS(D3DRS_BLENDOP, D3DBLENDOP_ADD);

			if( bDestAlphaColor == 2 ) {
				assert(usec==1);

				// all ones
				bNeedAlphaColor = 1;
				bNeedBlendFactorInAlpha = 1;
				vAlphaBlendColor.y = 0;
				vAlphaBlendColor.w = 1;
				SETRS(D3DRS_SRCBLEND, a.a != 0 ? D3DBLEND_INVSRCALPHA : D3DBLEND_SRCALPHA);
				SETRS(D3DRS_DESTBLEND, a.a != 0 ? D3DBLEND_SRCALPHA : D3DBLEND_INVSRCALPHA);
			}
			else {
				//b2XAlphaTest = 1;
				SETRS(D3DRS_SRCBLEND, a.a != 0 ? blendinvalpha[usec] : blendalpha[usec]);
				SETRS(D3DRS_DESTBLEND, a.a != 0 ? blendalpha[usec] : blendinvalpha[usec]);
			}
		}
	}

EndSetAlpha:
	//b2XAlphaTest = b2XAlphaTest && bNeedAlphaColor && !bNeedBlendFactorInAlpha;

	INC_ALPHAVARS();
}

void ZeroGS::SetWriteDepth()
{
	if( conf.mrtdepth ) {
		s_bWriteDepth = TRUE;
		s_nWriteDepthCount = 4;
	}
}

BOOL ZeroGS::IsWriteDepth()
{
	return s_bWriteDepth;
}

BOOL ZeroGS::IsWriteDestAlphaTest()
{
	return s_bWriteDepth;
}

void ZeroGS::SetDestAlphaTest()
{
	s_bDestAlphaTest = TRUE;
	s_nWriteDestAlphaTest = 4;
}

void ZeroGS::SetFogColor(u32 fog)
{
	if( 1||gs.fogcol != fog ) {
		gs.fogcol = fog;

		ZeroGS::Flush(0);
		ZeroGS::Flush(1);

		if( !g_bIsLost ) {
			DXVEC4 v;

			// set it immediately
			v.x = (gs.fogcol&0xff)/255.0f;
			v.y = ((gs.fogcol>>8)&0xff)/255.0f;
			v.z = ((gs.fogcol>>16)&0xff)/255.0f;
			SETCONSTF(GPU_FOGCOLOR, v);
		}
	}
}

void ZeroGS::ExtWrite()
{
	WARN_LOG("ExtWrite\n");

	// use local DISPFB, EXTDATA, EXTBUF, and PMODE
//	int bpp, start, end;
//	tex0Info texframe;

//	bpp = 4;
//	if( texframe.psm == 0x12 ) bpp = 3;
//	else if( texframe.psm & 2 ) bpp = 2;
//
//	// get the start and end addresses of the buffer
//	GetRectMemAddress(start, end, texframe.psm, 0, 0, texframe.tw, texframe.th, texframe.tbp0, texframe.tbw);
}

////////////
// Caches //
////////////
#ifdef __x86_64__
extern "C" void TestClutChangeMMX(void* src, void* dst, int entries, void* pret);
#endif

bool ZeroGS::CheckChangeInClut(u32 highdword, u32 psm)
{
	int cld = (highdword >> 29) & 0x7;
	int cbp = ((highdword >>  5) & 0x3fff);

	// processing the CLUT after tex0/2 are written
	switch(cld) {
		case 0: return false;
		case 1: break; // Seems to rarely not be 1.
		// note sure about changing cbp[0,1]
		case 4: return gs.cbp[0] != cbp;
		case 5: return gs.cbp[1] != cbp;

		// default: load
		default: break;
	}

	int cpsm = (highdword >> 19) & 0xe;
	int csm = (highdword >> 23) & 0x1;
	
	if( cpsm > 1 || csm )
		// don't support 16bit for now
		return true;

	int csa = (highdword >> 24) & 0x1f;

	int entries = (psm&3)==3 ? 256 : 16;

	u64* src = (u64*)(g_pbyGSMemory + cbp*256);
	u64* dst = (u64*)(g_pbyGSClut+64*csa);

	bool bRet = false;

#ifdef __x86_64__
	TestClutChangeMMX(dst, src, entries, &bRet);
#else
	int storeebx;
	// do a fast test with MMX
	__asm {
		mov storeebx, ebx
		mov edx, dst
		mov ecx, src
		mov ebx, entries

Start:
		movq mm0, [edx]
		movq mm1, [edx+8]
		pcmpeqd mm0, [ecx]
		pcmpeqd mm1, [ecx+16]

		movq mm2, [edx+16]
		movq mm3, [edx+24]
		pcmpeqd mm2, [ecx+32]
		pcmpeqd mm3, [ecx+48]

		pand mm0, mm1
		pand mm2, mm3
		movq mm4, [edx+32]
		movq mm5, [edx+40]
		pcmpeqd mm4, [ecx+8]
		pcmpeqd mm5, [ecx+24]

		pand mm0, mm2
		pand mm4, mm5
		movq mm6, [edx+48]
		movq mm7, [edx+56]
		pcmpeqd mm6, [ecx+40]
		pcmpeqd mm7, [ecx+56]
		
		pand mm0, mm4
		pand mm6, mm7
		pand mm0, mm6

		pmovmskb eax, mm0
		cmp eax, 0xff
		je Continue
		mov bRet, 1
		jmp Return

Continue:
		cmp ebx, 16
		jle Return

		test ebx, 0x10
		jz AddEcx
		sub ecx, 448 // go back and down one column, 
AddEcx:
		add ecx, 256 // go to the right block

		cmp ebx, 0x90
		jne Continue1
		add ecx, 256 // skip whole block
Continue1:
		add edx, 64
		sub ebx, 16
		jmp Start
	}

Return:
	__asm {
		emms
		mov ebx, storeebx
	}
#endif

	return bRet;
}

void ZeroGS::texClutWrite(int ctx)
{
	s_bTexFlush = 0;
	if( g_bIsLost )
		return;

	tex0Info& tex0 = vb[ctx].tex0;
	assert( PSMT_ISCLUT(tex0.psm) );

	// processing the CLUT after tex0/2 are written
	switch(tex0.cld) {
		case 0: return;
		case 1: break; // tex0.cld is usually 1.
		case 2: gs.cbp[0] = tex0.cbp; break;
		case 3: gs.cbp[1] = tex0.cbp; break;
		// not sure about changing cbp[0,1]
		case 4:
			if( gs.cbp[0] == tex0.cbp )
				return;
			gs.cbp[0] = tex0.cbp;
			break;
		case 5:
			if( gs.cbp[1] == tex0.cbp )
				return;
			gs.cbp[1] = tex0.cbp;
			break;
		default:  //DEBUG_LOG("cld isn't 0-5!");
			break;
	}

	Flush(!ctx);

	int entries = (tex0.psm&3)==3 ? 256 : 16;
if (tex0.csm) 
	{
		switch (tex0.cpsm)
		{
			// 16bit psm
			// eggomania uses non16bit textures for csm2
			case PSMCT16:
			{
				u16* src = (u16*)g_pbyGSMemory + tex0.cbp*128;
				u16 *dst = (u16*)(g_pbyGSClut+32*(tex0.csa&15)+(tex0.csa>=16?2:0));

				for (int i = 0; i < entries; ++i) 
				{
					*dst = src[getPixelAddress16_0(gs.clut.cou+i, gs.clut.cov, gs.clut.cbw)];
					dst += 2;
					
					// check for wrapping
					if (((u32)dst & 0x3ff) == 0) dst = (u16*)(g_pbyGSClut+2);
				}
				break;
			}
			
			case PSMCT16S:
			{
				u16* src = (u16*)g_pbyGSMemory + tex0.cbp*128;
				u16 *dst = (u16*)(g_pbyGSClut+32*(tex0.csa&15)+(tex0.csa>=16?2:0));

				for (int i = 0; i < entries; ++i) 
				{
					*dst = src[getPixelAddress16S_0(gs.clut.cou+i, gs.clut.cov, gs.clut.cbw)];
					dst += 2;
					
					// check for wrapping
					if (((u32)dst & 0x3ff) == 0) dst = (u16*)(g_pbyGSClut+2);
				}
			break;
			}
		
			case PSMCT32:
			case PSMCT24:
			{
				u32* src = (u32*)g_pbyGSMemory + tex0.cbp*64;
				u32 *dst = (u32*)(g_pbyGSClut+64*tex0.csa);
				
				// check if address exceeds src
				if( src+getPixelAddress32_0(gs.clut.cou+entries-1, gs.clut.cov, gs.clut.cbw) >= (u32*)g_pbyGSMemory + 0x00100000 ) 
					ERROR_LOG("texClutWrite out of bounds\n");
				else 
					for(int i = 0; i < entries; ++i) 
					{
						*dst = src[getPixelAddress32_0(gs.clut.cou+i, gs.clut.cov, gs.clut.cbw)];
						dst++;
					}
			}
			break;
			
			default:
			{
#ifndef RELEASE_TO_PUBLIC
			//DEBUG_LOG("unknown cpsm: %x (%x)\n", tex0.cpsm, tex0.psm);
#endif
				break;
			}
		}
	}
	else 
	{
		switch (tex0.cpsm)
		{
			case PSMCT24:
			case PSMCT32:
				if( entries == 16 )
					WriteCLUT_T32_I4_CSM1((u32*)(g_pbyGSMemory + tex0.cbp*256), (u32*)(g_pbyGSClut+64*tex0.csa));
				else 
					WriteCLUT_T32_I8_CSM1((u32*)(g_pbyGSMemory + tex0.cbp*256), (u32*)(g_pbyGSClut+64*tex0.csa));
				break;
				
			default:
				if( entries == 16 )
					WriteCLUT_T16_I4_CSM1((u32*)(g_pbyGSMemory + 256 * tex0.cbp), (u32*)(g_pbyGSClut+32*(tex0.csa&15)+(tex0.csa>=16?2:0)));
				else // sse2 for 256 is more complicated, so use regular
					WriteCLUT_T16_I8_CSM1_c((u32*)(g_pbyGSMemory + 256 * tex0.cbp), (u32*)(g_pbyGSClut+32*(tex0.csa&15)+(tex0.csa>=16?2:0)));
				break;
			
		}
	}
}

void ZeroGS::SetTexFlush()
{
	s_bTexFlush = TRUE;

//	if( PSMT_ISCLUT(vb[0].tex0.psm) )
//		texClutWrite(0);
//	if( PSMT_ISCLUT(vb[1].tex0.psm) )
//		texClutWrite(1);

	if( !s_bForceTexFlush ) 
	{
		if( s_ptexCurSet[0] != s_ptexNextSet[0] ) 
		{
			s_ptexCurSet[0] = s_ptexNextSet[0];
			pd3dDevice->SetTexture(SAMP_MEMORY0, s_ptexNextSet[0]);
		}
		if( s_ptexCurSet[1] != s_ptexNextSet[1] ) 
		{
			s_ptexCurSet[1] = s_ptexNextSet[1];
			pd3dDevice->SetTexture(SAMP_MEMORY1, s_ptexNextSet[1]);
		}
	}
}

int ZeroGS::Save(char* pbydata)
{
	if( pbydata == NULL )
		return 40 + 0x00400000 + sizeof(gs) + 2*VBSAVELIMIT + 2*sizeof(frameInfo) + 4 + 256*4;

	s_RTs.ResolveAll();
	s_DepthRTs.ResolveAll();
	
	vb[0].Unlock();
	vb[1].Unlock();
	
	strcpy(pbydata, libraryName);
	*(u32*)(pbydata+16) = ZEROGS_SAVEVER;
	pbydata += 32;

	*(int*)pbydata = icurctx; pbydata += 4;
	*(int*)pbydata = VBSAVELIMIT; pbydata += 4;

	memcpy(pbydata, g_pbyGSMemory, 0x00400000);
	pbydata += 0x00400000;

	memcpy(pbydata, g_pbyGSClut, 256*4);
	pbydata += 256*4;

	*(int*)pbydata = sizeof(gs);
	pbydata += 4;
	memcpy(pbydata, &gs, sizeof(gs));
	pbydata += sizeof(gs);

	for(int i = 0; i < 2; ++i) {
		memcpy(pbydata, &vb[i], VBSAVELIMIT);
		pbydata += VBSAVELIMIT;
	}

	vb[0].Lock();
	vb[1].Lock();

	return 0;
}

extern u32 s_uTex1Data[2][2], s_uClampData[2];
extern char *libraryName;
bool ZeroGS::Load(char* pbydata)
{
	memset(s_uTex1Data, 0, sizeof(s_uTex1Data));
	memset(s_uClampData, 0, sizeof(s_uClampData));

	// first 32 bytes are the id
	u32 savever = *(u32*)(pbydata+16);
	
	if( strncmp(pbydata, libraryName, 6) == 0 && (savever == ZEROGS_SAVEVER || savever == 0xaa000004) ) {

		g_MemTargs.Destroy();

		GSStateReset();
		pbydata += 32;

		int context = *(int*)pbydata; pbydata += 4;
		u32 savelimit = VBSAVELIMIT;
		
		savelimit = *(u32*)pbydata; pbydata += 4;

		memcpy(g_pbyGSMemory, pbydata, 0x00400000);
		pbydata += 0x00400000;

		memcpy(g_pbyGSClut, pbydata, 256*4);
		pbydata += 256*4;

		memset(&gs, 0, sizeof(gs));

		int savedgssize;
		if( savever == 0xaa000004 )
			savedgssize = 0x1d0;
		else {
			savedgssize = *(int*)pbydata;
			pbydata += 4;
		}

		memcpy(&gs, pbydata, savedgssize);
		pbydata += savedgssize;
		prim = &gs._prim[gs.prac];

		LPD3DVB pvb = vb[0].pvb;
		memcpy(&vb[0], pbydata, min(savelimit, VBSAVELIMIT));
		pbydata += savelimit;
		vb[0].pvb = pvb;

		pvb = vb[1].pvb;
		memcpy(&vb[1], pbydata, min(savelimit, VBSAVELIMIT));
		pbydata += savelimit;
		vb[1].pvb = pvb;

		for(int i = 0; i < 2; ++i) {
			vb[i].bNeedZCheck = vb[i].bNeedFrameCheck = 1;
			
			vb[i].bSyncVars = 0; vb[i].bNeedTexCheck = 1;
			memset(vb[i].uCurTex0Data, 0, sizeof(vb[i].uCurTex0Data));
		}

		icurctx = -1;
		
		pd3dDevice->SetRenderTarget(0, psurfOrgTarg);
		pd3dDevice->SetRenderTarget(1, NULL);
		pd3dDevice->SetDepthStencilSurface(psurfOrgDepth);
		SetFogColor(gs.fogcol);

		vb[0].Lock();
		vb[1].Lock();

		return true;
	}

	return false;
}

void ZeroGS::SaveSnapshot(const char* filename)
{
	g_bMakeSnapshot = 1;
	strSnapshot = filename;
}

// AVI capture stuff
bool ZeroGS::StartCapture()
{
	if( !s_aviinit ) {
		START_AVI("zerogs.avi");

		assert( s_ptexAVICapture == NULL );
		if( FAILED(pd3dDevice->CreateOffscreenPlainSurface(width, height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &s_ptexAVICapture, NULL)) ) {
			STOP_AVI();
			ZeroGS::AddMessage("Failed to create avi");
			return false;
		}

		s_aviinit = 1;
	}
	else {
		DEBUG_LOG("ZeroGS: Continuing from previous capture");
	}

	s_avicapturing = 1;
	return true;
}

void ZeroGS::StopCapture()
{
	s_avicapturing = 0;
}

void ZeroGS::CaptureFrame()
{
	assert( s_avicapturing && s_aviinit && s_ptexAVICapture != NULL );

	vector<BYTE> mem;

	pd3dDevice->GetRenderTargetData(psurfOrgTarg, s_ptexAVICapture);
	
	D3DLOCKED_RECT lock;
	mem.resize(width * height * 4);

	s_ptexAVICapture->LockRect(&lock, NULL, D3DLOCK_READONLY);
	assert( lock.Pitch == width*4 );

	BYTE* pend = (BYTE*)lock.pBits + (conf.height-1)*width*4;
	for(int i = 0; i < conf.height; ++i) {
		memcpy_amd(&mem[width*4*i], pend - width*4*i, width * 4);
	}
	s_ptexAVICapture->UnlockRect();

	int fps = SMODE1->CMOD == 3 ? 50 : 60;
	bool bSuccess = ADD_FRAME_FROM_DIB_TO_AVI("AAAA", fps, width, height, 32, &mem[0]);

	if( !bSuccess ) {
		s_avicapturing = 0;
		STOP_AVI();
		s_aviinit = 0;
		ZeroGS::AddMessage("Failed to create avi");
		return;
	}
}
