/***************************************************************************
*   Copyright (C) 2010 PCSX4ALL Team                                      *
*   Copyright (C) 2010 Unai                                               *
*                                                                         *
*   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.,                                       *
*   51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA.           *
***************************************************************************/

#include "port.h"
#include "gpu.h"
#include "profiler.h"
#include "debug.h"

int skipCount = 2; /* frame skip (0,1,2,3...) */
int skCount = 0; /* internal frame skip */
int linesInterlace = 0;  /* internal lines interlace */
int linesInterlace_user = 0; /* Lines interlace */

bool isSkip = false; /* skip frame (info coming from GPU) */
bool wasSkip = false;
bool skipFrame = false; /* skip frame (according to frame skip) */
bool alt_fps = false; /* Alternative FPS algorithm */
bool show_fps = false; /* Show FPS statistics */

bool isPAL = false; /* PAL video timing */
bool progressInterlace_flag = false; /* Progressive interlace flag */
bool progressInterlace = false; /* Progressive interlace option*/
bool frameLimit = false; /* frames to wait */

bool light = true; /* lighting */
bool blend = true; /* blending */
bool FrameToRead = false; /* load image in progress */
bool FrameToWrite = false; /* store image in progress */
bool fb_dirty = false;

bool enableAbbeyHack = false; /* Abe's Odyssey hack */

u8 BLEND_MODE;
u8 TEXT_MODE;
u8 Masking;

u16 PixelMSB;
u16 PixelData;

///////////////////////////////////////////////////////////////////////////////
//  GPU Global data
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//  Dma Transfers info
s32		px,py;
s32		x_end,y_end;
u16*  pvram;

u32 GP0;
s32 PacketCount;
s32 PacketIndex;

///////////////////////////////////////////////////////////////////////////////
//  Display status
u32 DisplayArea   [6];

///////////////////////////////////////////////////////////////////////////////
//  Rasterizer status
u32 TextureWindow [4];
u32 DrawingArea   [4];
u32 DrawingOffset [2];

///////////////////////////////////////////////////////////////////////////////
//  Rasterizer status

u16* TBA;
u16* CBA;

///////////////////////////////////////////////////////////////////////////////
//  Inner Loops
s32   u4, du4;
s32   v4, dv4;
s32   r4, dr4;
s32   g4, dg4;
s32   b4, db4;
u32   lInc;
u32   tInc, tMsk;

GPUPacket PacketBuffer;
// FRAME_BUFFER_SIZE is defined in bytes; 512K is guard memory for out of range reads
u16   GPU_FrameBuffer[(FRAME_BUFFER_SIZE+512*1024)/2] __attribute__((aligned(2048)));
u32   GPU_GP1;

///////////////////////////////////////////////////////////////////////////////
//  Inner loop driver instanciation file
#include "gpu_inner.h"

///////////////////////////////////////////////////////////////////////////////
//  GPU Raster Macros
#define	GPU_RGB16(rgb)        ((((rgb)&0xF80000)>>9)|(((rgb)&0xF800)>>6)|(((rgb)&0xF8)>>3))

#define GPU_EXPANDSIGN(x)  (((s32)(x)<<21)>>21)

#define CHKMAX_X 1024
#define CHKMAX_Y 512

#define	GPU_SWAP(a,b,t)	{(t)=(a);(a)=(b);(b)=(t);}

///////////////////////////////////////////////////////////////////////////////
// GPU internal image drawing functions
#include "gpu_raster_image.h"

///////////////////////////////////////////////////////////////////////////////
// GPU internal line drawing functions
#include "gpu_raster_line.h"

///////////////////////////////////////////////////////////////////////////////
// GPU internal polygon drawing functions
#include "gpu_raster_polygon.h"

///////////////////////////////////////////////////////////////////////////////
// GPU internal sprite drawing functions
#include "gpu_raster_sprite.h"

///////////////////////////////////////////////////////////////////////////////
// GPU command buffer execution/store
#include "gpu_command.h"

///////////////////////////////////////////////////////////////////////////////
INLINE void gpuReset(void)
{
	GPU_GP1 = 0x14802000;
	TextureWindow[0] = 0;
	TextureWindow[1] = 0;
	TextureWindow[2] = 255;
	TextureWindow[3] = 255;
	DrawingArea[2] = 256;
	DrawingArea[3] = 240;
	DisplayArea[2] = 256;
	DisplayArea[3] = 240;
	DisplayArea[5] = 240;
}

///////////////////////////////////////////////////////////////////////////////
bool  GPU_init(void)
{
	gpuReset();
	
	// s_invTable
	for(int i=1;i<=(1<<TABLE_BITS);++i)
	{
		double v = 1.0 / double(i);
		#ifdef GPU_TABLE_10_BITS
		v *= double(0xffffffff>>1);
		#else
		v *= double(0x80000000);
		#endif
		s_invTable[i-1]=s32(v);
	}
	return (0);
}

///////////////////////////////////////////////////////////////////////////////
void  GPU_shutdown(void)
{
}

///////////////////////////////////////////////////////////////////////////////
long  GPU_freeze(unsigned int bWrite, GPUFreeze_t* p2)
{
	if (!p2) return (0);
	if (p2->Version != 1) return (0);

	if (bWrite)
	{
		p2->GPU_gp1 = GPU_GP1;
		memset(p2->Control, 0, sizeof(p2->Control));
		// save resolution and registers for P.E.Op.S. compatibility
		p2->Control[3] = (3 << 24) | ((GPU_GP1 >> 23) & 1);
		p2->Control[4] = (4 << 24) | ((GPU_GP1 >> 29) & 3);
		p2->Control[5] = (5 << 24) | (DisplayArea[0] | (DisplayArea[1] << 10));
		p2->Control[6] = (6 << 24) | (2560 << 12);
		p2->Control[7] = (7 << 24) | (DisplayArea[4] | (DisplayArea[5] << 10));
		p2->Control[8] = (8 << 24) | ((GPU_GP1 >> 17) & 0x3f) | ((GPU_GP1 >> 10) & 0x40);
		memcpy(p2->FrameBuffer, (u16*)GPU_FrameBuffer, FRAME_BUFFER_SIZE);
		return (1);
	}
	else
	{
		GPU_GP1 = p2->GPU_gp1;
		memcpy((u16*)GPU_FrameBuffer, p2->FrameBuffer, FRAME_BUFFER_SIZE);
		GPU_writeStatus((5 << 24) | p2->Control[5]);
		GPU_writeStatus((7 << 24) | p2->Control[7]);
		GPU_writeStatus((8 << 24) | p2->Control[8]);
		gpuSetTexture(GPU_GP1);
		return (1);
	}
	return (0);
}

///////////////////////////////////////////////////////////////////////////////
//  GPU DMA comunication

///////////////////////////////////////////////////////////////////////////////
u8 PacketSize[256] =
{
	0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//		0-15
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//		16-31
	3, 3, 3, 3, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8, 8, 8,	//		32-47
	5, 5, 5, 5, 8, 8, 8, 8, 7, 7, 7, 7, 11, 11, 11, 11,	//	48-63
	2, 2, 2, 2, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,	//		64-79
	3, 3, 3, 3, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4,	//		80-95
	2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 2, 2,	//		96-111
	1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,	//		112-127
	3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//		128-
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//		144
	2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//		160
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//
	2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	//
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0	//
};

///////////////////////////////////////////////////////////////////////////////
INLINE void gpuSendPacket()
{
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_sendPacket++;
#endif
	gpuSendPacketFunction(PacketBuffer.U4[0]>>24);
}

///////////////////////////////////////////////////////////////////////////////
INLINE void gpuCheckPacket(u32 uData)
{
	if (PacketCount)
	{
		PacketBuffer.U4[PacketIndex++] = uData;
		--PacketCount;
	}
	else
	{
		PacketBuffer.U4[0] = uData;
		PacketCount = PacketSize[uData >> 24];
		PacketIndex = 1;
	}
	if (!PacketCount) gpuSendPacket();
}

///////////////////////////////////////////////////////////////////////////////
void  GPU_writeDataMem(u32* dmaAddress, s32 dmaCount)
{
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_writeDataMem++;
#endif
	pcsx4all_prof_pause(PCSX4ALL_PROF_CPU);
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	u32 data;
	const u16 *VIDEO_END=(GPU_FrameBuffer+(FRAME_BUFFER_SIZE/2)-1);
	GPU_GP1 &= ~0x14000000;

	while (dmaCount) 
	{
		if (FrameToWrite) 
		{
			while (dmaCount)
			{
				dmaCount--;
				data = *dmaAddress++;
				if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
				pvram[px] = data;
				if (++px>=x_end) 
				{
					px = 0;
					pvram += 1024;
					if (++py>=y_end) 
					{
						FrameToWrite = false;
						GPU_GP1 &= ~0x08000000;
						break;
					}
				}
				if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
				pvram[px] = data>>16;
				if (++px>=x_end) 
				{
					px = 0;
					pvram += 1024;
					if (++py>=y_end) 
					{
						FrameToWrite = false;
						GPU_GP1 &= ~0x08000000;
						break;
					}
				}
			}
		}
		else
		{
			data = *dmaAddress++;
			dmaCount--;
			gpuCheckPacket(data);
		}
	}

	GPU_GP1 = (GPU_GP1 | 0x14000000) & ~0x60000000;
	fb_dirty = true;
	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	pcsx4all_prof_resume(PCSX4ALL_PROF_CPU);
}

u32 *lUsedAddr[3];
INLINE int CheckForEndlessLoop(u32 *laddr)
{
	if(laddr==lUsedAddr[1]) return 1;
	if(laddr==lUsedAddr[2]) return 1;

	if(laddr<lUsedAddr[0]) lUsedAddr[1]=laddr;
	else                   lUsedAddr[2]=laddr;
	lUsedAddr[0]=laddr;
	return 0;
}

///////////////////////////////////////////////////////////////////////////////
long GPU_dmaChain(u32* baseAddr, u32 dmaVAddr)
{
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_dmaChain++;
#endif
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	u32 data, *address, count, offset;
	unsigned int DMACommandCounter = 0;
	long dma_words = 0;

	GPU_GP1 &= ~0x14000000;
	lUsedAddr[0]=lUsedAddr[1]=lUsedAddr[2]=(u32*)0x1fffff;
	dmaVAddr &= 0x001FFFFF;
	while (dmaVAddr != 0x1FFFFF)
	{
		address = (baseAddr + (dmaVAddr >> 2));
		if(DMACommandCounter++ > 2000000) break;
		if(CheckForEndlessLoop(address)) break;
		data = *address++;
		count = (data >> 24);
		offset = data & 0x001FFFFF;
		if (dmaVAddr != offset) dmaVAddr = offset;
		else dmaVAddr = 0x1FFFFF;

		if(count>0) GPU_writeDataMem(address,count);
		dma_words += 1 + count;
	}
	GPU_GP1 = (GPU_GP1 | 0x14000000) & ~0x60000000;
	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);

	return dma_words;
}

///////////////////////////////////////////////////////////////////////////////
void  GPU_writeData(u32 data)
{
	const u16 *VIDEO_END=(GPU_FrameBuffer+(FRAME_BUFFER_SIZE/2)-1);
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_writeData++;
#endif
	pcsx4all_prof_pause(PCSX4ALL_PROF_CPU);
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	GPU_GP1 &= ~0x14000000;

	if (FrameToWrite)
	{
		if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
		pvram[px]=(u16)data;
		if (++px>=x_end)
		{
			px = 0;
			pvram += 1024;
			if (++py>=y_end) 
			{
				FrameToWrite = false;
				GPU_GP1 &= ~0x08000000;
			}
		}
		if (FrameToWrite)
		{
			if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
			pvram[px]=data>>16;
			if (++px>=x_end)
			{
				px = 0;
				pvram += 1024;
				if (++py>=y_end) 
				{
					FrameToWrite = false;
					GPU_GP1 &= ~0x08000000;
				}
			}
		}
	}
	else
	{
		gpuCheckPacket(data);
	}
	GPU_GP1 |= 0x14000000;
	fb_dirty = true;
	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	pcsx4all_prof_resume(PCSX4ALL_PROF_CPU);

}


///////////////////////////////////////////////////////////////////////////////
void  GPU_readDataMem(u32* dmaAddress, s32 dmaCount)
{
	const u16 *VIDEO_END=(GPU_FrameBuffer+(FRAME_BUFFER_SIZE/2)-1);
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_readDataMem++;
#endif
	if(!FrameToRead) return;

	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	GPU_GP1 &= ~0x14000000;
	do 
	{
		if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
		// lower 16 bit
		u32 data = pvram[px];

		if (++px>=x_end) 
		{
			px = 0;
			pvram += 1024;
		}

		if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
		// higher 16 bit (always, even if it's an odd width)
		data |= (u32)(pvram[px])<<16;
		
		*dmaAddress++ = data;

		if (++px>=x_end) 
		{
			px = 0;
			pvram += 1024;
			if (++py>=y_end) 
			{
				FrameToRead = false;
				GPU_GP1 &= ~0x08000000;
				break;
			}
		}
	} while (--dmaCount);

	GPU_GP1 = (GPU_GP1 | 0x14000000) & ~0x60000000;
	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
}



///////////////////////////////////////////////////////////////////////////////
u32  GPU_readData(void)
{
	const u16 *VIDEO_END=(GPU_FrameBuffer+(FRAME_BUFFER_SIZE/2)-1);
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_readData++;
#endif
	pcsx4all_prof_pause(PCSX4ALL_PROF_CPU);
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_READ);
	GPU_GP1 &= ~0x14000000;
	if (FrameToRead)
	{
		if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
		GP0 = pvram[px];
		if (++px>=x_end)
		{
			px = 0;
			pvram += 1024;
			if (++py>=y_end) 
			{
				FrameToRead = false;
				GPU_GP1 &= ~0x08000000;
			}
		}
		if ((&pvram[px])>(VIDEO_END)) pvram-=512*1024;
		GP0 |= pvram[px]<<16;
		if (++px>=x_end)
		{
			px = 0;
			pvram +=1024;
			if (++py>=y_end) 
			{
				FrameToRead = false;
				GPU_GP1 &= ~0x08000000;
			}
		}

	}
	GPU_GP1 |= 0x14000000;

	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_READ);
	pcsx4all_prof_resume(PCSX4ALL_PROF_CPU);
	return (GP0);
}

///////////////////////////////////////////////////////////////////////////////
u32     GPU_readStatus(void)
{
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_readStatus++;
#endif
	return GPU_GP1;
}

///////////////////////////////////////////////////////////////////////////////
void  GPU_writeStatus(u32 data)
{
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_writeStatus++;
#endif
	pcsx4all_prof_pause(PCSX4ALL_PROF_CPU);
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	switch (data >> 24) {
	case 0x00:
		gpuReset();
		break;
	case 0x01:
		GPU_GP1 &= ~0x08000000;
		PacketCount = 0; FrameToRead = FrameToWrite = false;
		break;
	case 0x02:
		GPU_GP1 &= ~0x08000000;
		PacketCount = 0; FrameToRead = FrameToWrite = false;
		break;
	case 0x03:
		GPU_GP1 = (GPU_GP1 & ~0x00800000) | ((data & 1) << 23);
		break;
	case 0x04:
		if (data == 0x04000000)
		PacketCount = 0;
		GPU_GP1 = (GPU_GP1 & ~0x60000000) | ((data & 3) << 29);
		break;
	case 0x05:
		DisplayArea[0] = (data & 0x000003FF); //(short)(data & 0x3ff);
		DisplayArea[1] = ((data & 0x0007FC00)>>10); //(data & 0x000FFC00) >> 10; //(short)((data>>10)&0x1ff);
		fb_dirty = true;
		wasSkip = isSkip;
		if (isSkip)
			isSkip = false;
		else
			isSkip = skipFrame;
		break;
	case 0x07:
		DisplayArea[4] = data & 0x000003FF; //(short)(data & 0x3ff);
		DisplayArea[5] = (data & 0x000FFC00) >> 10; //(short)((data>>10) & 0x3ff);
		fb_dirty = true;
		break;
	case 0x08:
		{
			GPU_GP1 = (GPU_GP1 & ~0x007F0000) | ((data & 0x3F) << 17) | ((data & 0x40) << 10);
			static u32 HorizontalResolution[8] = { 256, 368, 320, 384, 512, 512, 640, 640 };
			DisplayArea[2] = HorizontalResolution[(GPU_GP1 >> 16) & 7];
			static u32 VerticalResolution[4] = { 240, 480, 256, 480 };
			DisplayArea[3] = VerticalResolution[(GPU_GP1 >> 19) & 3];
			isPAL = (data & 0x08) ? true : false; // if 1 - PAL mode, else NTSC
		}
		fb_dirty = true;
		break;
	case 0x10:
		switch (data & 0xffff) {
		case 0:
		case 1:
		case 3:
			GP0 = (DrawingArea[1] << 10) | DrawingArea[0];
			break;
		case 4:
			GP0 = ((DrawingArea[3]-1) << 10) | (DrawingArea[2]-1);
			break;
		case 6:
		case 5:
			GP0 = (DrawingOffset[1] << 11) | DrawingOffset[0];
			break;
		case 7:
			GP0 = 2;
			break;
		default:
			GP0 = 0;
		}
		break;
	}
	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_HW_WRITE);
	pcsx4all_prof_resume(PCSX4ALL_PROF_CPU);
}

#ifndef REARMED

// Blitting functions
#include "gpu_blit.h"

INLINE void gpuVideoOutput(void)
{
	static s16 old_res_horz, old_res_vert, old_rgb24;
	s16 h0, x0, y0, w0, h1;

	x0 = DisplayArea[0];
	y0 = DisplayArea[1];

	w0 = DisplayArea[2];
	h0 = DisplayArea[3];  // video mode

	h1 = DisplayArea[5] - DisplayArea[4]; // display needed
	if (h0 == 480) h1 = Min2(h1*2,480);

	u16* dest_screen16 = SCREEN;
	u16* src_screen16  = &((u16*)GPU_FrameBuffer)[FRAME_OFFSET(x0,y0)];
	u32 isRGB24 = (GPU_GP1 & 0x00200000 ? 32 : 0);

	/* Clear the screen if resolution changed to prevent interlacing and clipping to clash */
	if( (w0 != old_res_horz || h1 != old_res_vert || (s16)isRGB24 != old_rgb24) )
	{
		// Update old resolution
		old_res_horz = w0;
		old_res_vert = h1;
		old_rgb24 = (s16)isRGB24;
		// Finally, clear the screen for this special case
		video_clear();
	}

	//  Height centering
	int sizeShift = 1;
	if(h0==256) h0 = 240; else if(h0==480) sizeShift = 2;
	if(h1>h0) { src_screen16 += ((h1-h0)>>sizeShift)*1024; h1 = h0; }
	else if(h1<h0) dest_screen16 += ((h0-h1)>>sizeShift)*VIDEO_WIDTH;

	/* Main blitter */
	int incY = (h0==480) ? 2 : 1;
	h0=(h0==480 ? 2048 : 1024);

	{
		const int li=linesInterlace;
		bool pi=progressInterlace;
		bool pif=progressInterlace_flag;
		switch ( w0 )
		{
			case 256:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWWDWW(	src_screen16,	dest_screen16, isRGB24);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
			case 368:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWWWWWWWWS(	src_screen16,	dest_screen16, isRGB24, 4);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
			case 320:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWW(	src_screen16,	dest_screen16, isRGB24);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
			case 384:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWWWWWS(	src_screen16,	dest_screen16, isRGB24);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
			case 512:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWWSWWSWS(	src_screen16, dest_screen16, isRGB24);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
			case 640:
				for(int y1=y0+h1; y0<y1; y0+=incY)
				{
					if(( 0 == (y0&li) ) && ((!pi) || (pif=!pif))) GPU_BlitWS(	src_screen16, dest_screen16, isRGB24);
					dest_screen16 += VIDEO_WIDTH;
					src_screen16  += h0;
				}
				break;
		}
		progressInterlace_flag=!progressInterlace_flag;
	}
	video_flip();
}

///////////////////////////////////////////////////////////////////////////////
void  GPU_updateLace(void)
{
#ifdef  ENABLE_GPU_LOG_SUPPORT
	fprintf(stdout,"GPU_updateLace()\n");
#endif
#ifdef DEBUG_ANALYSIS
	dbg_anacnt_GPU_updateLace++;
#endif
	pcsx4all_prof_start_with_pause(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_COUNTERS);
#ifdef PROFILER_PCSX4ALL
	pcsx4all_prof_frames++;
#endif
#ifdef DEBUG_FRAME
	if(isdbg_frame())
	{
		static int passed=0;
		if (!passed) dbg_enable();
		else pcsx4all_exit();
		passed++;
	}
#endif

	// Frame skip table
	static const unsigned char skipTable[12][12] =
	{
		{ 0,0,0,0,0,0,0,0,0,0,0,0 },
		{ 0,0,0,0,0,0,0,0,0,0,0,1 },
		{ 0,0,0,0,0,1,0,0,0,0,0,1 },
		{ 0,0,0,1,0,0,0,1,0,0,0,1 },
		{ 0,0,1,0,0,1,0,0,1,0,0,1 },
		{ 0,1,0,0,1,0,1,0,0,1,0,1 },
		{ 0,1,0,1,0,1,0,1,0,1,0,1 },
		{ 0,1,0,1,1,0,1,0,1,1,0,1 },
		{ 0,1,1,0,1,1,0,1,1,0,1,1 },
		{ 0,1,1,1,0,1,1,1,0,1,1,1 },
		{ 0,1,1,1,1,1,0,1,1,1,1,1 },
		{ 0,1,1,1,1,1,1,1,1,1,1,1 }
	};
	
	// Interlace bit toggle
	GPU_GP1 ^= 0x80000000;

	// Update display
	if ((!skipFrame) && (!isSkip) && (fb_dirty) && (!(((GPU_GP1&0x08000000))||((GPU_GP1&0x00800000)))))
	{
		gpuVideoOutput(); // Display updated

		if (DisplayArea[3] == 480)
		{
			if (linesInterlace_user) linesInterlace = 3; // 1/4 of lines
			else linesInterlace = 1; // if 480 we only need half of lines
		}
		else if (linesInterlace != linesInterlace_user)
		{
			linesInterlace = linesInterlace_user; // resolution changed from 480 to lower one
			video_clear();
		}
	}

	// Limit FPS
	if (frameLimit)
	{
		static unsigned next=get_ticks();
		if (!skipFrame)
		{
			unsigned now=get_ticks();
			if (now<next) wait_ticks(next-now);
		}
		next+=(isPAL?(1000000/50):((unsigned)(1000000.0/59.94)));
	}

	// Show FPS statistics
	if (show_fps)
	{
		static u32 real_fps=0;
		static u32 prev=get_ticks();
		static char msg[32]="FPS=000/00 SPD=000%";
		u32 now=get_ticks();
		real_fps++;
		if ((now-prev)>=1000000)
		{
			u32 expected_fps=(isPAL?50:60);
			sprintf(msg,"FPS=%3d/%2d SPD=%3d%%",((real_fps*(12-skipCount))/12),((expected_fps*(12-skipCount))/12),((real_fps*100)/expected_fps));
			prev=now;
			real_fps=0;
		}
		port_printf(5,5,msg);
	}

	// Update frame-skip
	if (!alt_fps)
	{
		// Video frame-skip
		skipFrame=skipTable[skipCount][skCount];
		skCount--; if (skCount<0) skCount=11;
		isSkip=skipFrame;
	}
	else
	{
		// Game frame-skip
		if (!isSkip)
		{
			skipFrame=skipTable[skipCount][skCount];
			skCount--; if (skCount<0) skCount=11;
			isSkip=true;
		}
	}
	fb_dirty=false;

	pcsx4all_prof_end_with_resume(PCSX4ALL_PROF_GPU,PCSX4ALL_PROF_COUNTERS);
}

#else

#include "../../frontend/plugin_lib.h"

extern "C" {

static const struct rearmed_cbs *cbs;
static s16 old_res_horz, old_res_vert, old_rgb24;

static void blit(void)
{
	u16 *base = (u16 *)GPU_FrameBuffer;
	s16 isRGB24 = (GPU_GP1 & 0x00200000) ? 1 : 0;
	s16 h0, x0, y0, w0, h1;

	x0 = DisplayArea[0] & ~1; // alignment needed by blitter
	y0 = DisplayArea[1];
	base += FRAME_OFFSET(x0, y0);

	w0 = DisplayArea[2];
	h0 = DisplayArea[3];  // video mode

	h1 = DisplayArea[5] - DisplayArea[4]; // display needed
	if (h0 == 480) h1 = Min2(h1*2,480);

	if (h1 <= 0)
		return;

	if (w0 != old_res_horz || h1 != old_res_vert || isRGB24 != old_rgb24)
	{
		old_res_horz = w0;
		old_res_vert = h1;
		old_rgb24 = (s16)isRGB24;
		cbs->pl_vout_set_mode(w0, h1, w0, h1, isRGB24 ? 24 : 16);
	}

	cbs->pl_vout_flip(base, 1024, isRGB24, w0, h1);
}

void GPU_updateLace(void)
{
	// Interlace bit toggle
	GPU_GP1 ^= 0x80000000;

	if (!fb_dirty || (GPU_GP1&0x08800000))
		return;

	if (!wasSkip) {
		blit();
		fb_dirty = false;
		skCount = 0;
	}
	else {
		skCount++;
		if (skCount >= 8)
			wasSkip = isSkip = 0;
	}

	skipFrame = cbs->fskip_advice || cbs->frameskip == 1;
}

long GPUopen(unsigned long *, char *, char *)
{
	cbs->pl_vout_open();
	return 0;
}

long GPUclose(void)
{
	cbs->pl_vout_close();
	return 0;
}

long GPUfreeze(unsigned int ulGetFreezeData, GPUFreeze_t* p2)
{
	if (ulGetFreezeData > 1)
		return 0;

	return GPU_freeze(ulGetFreezeData, p2);
}

void GPUrearmedCallbacks(const struct rearmed_cbs *cbs_)
{
	enableAbbeyHack = cbs_->gpu_unai.abe_hack;
	light = !cbs_->gpu_unai.no_light;
	blend = !cbs_->gpu_unai.no_blend;
	if (cbs_->pl_vout_set_raw_vram)
		cbs_->pl_vout_set_raw_vram((void *)GPU_FrameBuffer);

	cbs = cbs_;
	if (cbs->pl_set_gpu_caps)
		cbs->pl_set_gpu_caps(0);
}

} /* extern "C" */

#endif
